Configuration¶
Core is a Spring Boot application: every property can be set in YAML or as an
environment variable (Spring's relaxed binding: app.storage.type becomes
APP_STORAGE_TYPE). The Compose tiers take their settings from
deploy/compose/.env (documented, grouped, generated by
./scripts/vegaduta secrets); Helm takes them from chart values.
Source of truth for defaults: core/app-config/src/main/resources/application*.yml.
Line numbers drift; search for the property name.
Spring profiles¶
Profiles switch whole feature areas on or off at boot. Set them with
SPRING_PROFILES_ACTIVE.
| Profile | Turns on | File |
|---|---|---|
security |
Keycloak JWT resource server, admin client, RBAC | application-security.yml |
knowledge |
RAG on pgvector, embeddings, reranking, upload limits | application-knowledge.yml |
graph-memory |
graph memory on Apache AGE | (beans only) |
workflow |
Kafka/Redpanda workflow events, Telegram, workflow mail | application-workflow.yml |
temporal |
Temporal workflow engine client | (beans only) |
mcp |
static stdio MCP servers shared by all tenants (spawned by mcp-stdio-runner when MCP_STDIO_SIDECAR_ENABLED=true, else inside core, which then needs the core-full image) |
application-mcp.yml |
mcpserver |
the platform's own MCP server at /mcp |
application-mcpserver.yml |
lite |
single-process laptop mode: tracing off, local-disk storage, in-process workflow engine, 2 s Redis timeout, mail/Telegram without the workflow profile |
application-lite.yml |
personal |
one person on one machine: local sign-in instead of Keycloak, taint tracking enforced, STDIO MCP servers under an OS sandbox, skill learning on, billing off, tracing off, local-disk storage, in-process workflow engine, small connection pools. See Personal mode and Personal agent settings | application-personal.yml |
shape-api / shape-jobs |
split deployment: the API skips scheduled work that the jobs shape owns | application-shape-*.yml |
shape-jobs-core / shape-jobs-ingest |
pools of the jobs shape: ingest runs only the knowledge pollers, core runs the rest. Activate after shape-jobs; see Deployment shapes. The Helm chart now starts the main jobs pod as plain shape-jobs with APP_SCHEDULING_OFFLOADED_POOLS; shape-jobs-core stays as the hand-run equivalent of offloading ingest |
application-shape-jobs-*.yml |
shape-jobs-pool |
a named pool of the jobs shape (webhooks, intake, billing), chosen by APP_SCHEDULING_POOL. Activate after shape-jobs |
application-shape-jobs-pool.yml |
jobs / worker |
set by the app-jobs / app-worker shapes (Flyway off, own HTTP port) |
in those modules |
e2e |
relaxed signup/abuse limits for the Playwright suite only | application-e2e.yml |
Defaults by where core runs:
| Where | SPRING_PROFILES_ACTIVE |
|---|---|
application.yml (bare JVM) |
security,knowledge,workflow,graph-memory |
core image (core/Dockerfile, default MCP_TOOLCHAIN=none) |
security,knowledge,workflow,graph-memory |
core-full image (MCP_TOOLCHAIN=full) |
security,knowledge,workflow,graph-memory,mcp |
Lite (deploy/compose/lite.yml) |
security,knowledge,graph-memory,lite (plus ,mcp with CORE_IMAGE_VARIANT=-full if built-in STDIO servers should spawn in core instead of the runner) |
Personal (deploy/compose/personal.yml, scripts/vegaduta personal) |
security,knowledge,graph-memory,personal (override with PERSONAL_SPRING_PROFILES) |
Full (deploy/compose/docker-compose.yml) |
security,knowledge,graph-memory,mcpserver plus CORE_MCP_PROFILE (the CLI sets ,mcp for the tools, browser and sandbox profiles) plus CORE_PROFILES_EXTRA (the CLI adds ,workflow or ,workflow,temporal for those profiles) plus CORE_SHAPE_PROFILE (set it to ,shape-api yourself when you start the jobs profile so the API leaves the background work to core-jobs; empty by default) |
Helm (charts/vegaduta) |
global.core.springProfiles (security,knowledge,workflow,graph-memory,mcpserver); ,mcp is added when global.components.mcpStdioRunner=true (every core role) or global.core.mcpToolchain=full (API only) |
Core shapes¶
One image, one reactor, four ways to run it. Build with
docker build -f core/Dockerfile --build-arg MODULE=<module> core.
| Shape | Module / main class | Role |
|---|---|---|
| api | app / CoreApplication |
REST, SSE, WebSocket, MCP server, webhooks; the chat path |
| jobs | app-jobs / JobsApplication |
scheduled sweeps and pollers; run exactly one |
| worker | app-worker / WorkerApplication |
Temporal workflow worker |
| migrate | app with app.tenancy.migrate-only=true |
runs Flyway for the control plane and every tenant schema, then exits; no @Scheduled job, Kafka consumer or Temporal worker runs in it |
app.tenancy.migrate-on-boot (default true) makes the API migrate tenant
schemas at start-up; set it to false when a separate migrate step runs (Helm
does this). Env names: APP_TENANCY_MIGRATE_ON_BOOT, APP_TENANCY_MIGRATE_ONLY.
The migrate shape exits 0 when every tenant is current, 1 when a tenant failed (the Job fails
the release) and 75 when another migrator holds the advisory lock tenant-schema-migrate-only
(a second concurrent Job; the Helm Job ignores the pod and starts a new one, without spending
migrate.backoffLimit). A 75 with no other migrator running is a bug, not a lock: report it.
Compose has no separate migrate step; core migrates on boot there.
Essential vs plug-and-play services¶
The platform runs on a small essential set. Everything else is plug-and-play:
off by default, switched on per deployment, and core degrades cleanly when it
is absent. The default core image is slim (core/Dockerfile
MCP_TOOLCHAIN=none: JRE plus a bare node binary for the MCP relay, no
npm/npx, uv or browsers). The in-core toolchain that made the image about 6 GB
now lives in mcp-stdio-runner (and its -browser variant), or in the opt-in
core-full image.
| Service | Class | Enables | Compose (full tier) | Helm (charts/vegaduta) |
Core flag |
|---|---|---|---|---|---|
| core API | essential | REST, SSE, WebSocket, chat path, MCP server at /mcp |
always | always | — |
| web | essential | tenant console | always | global.components.web |
— |
| postgres (pgvector + AGE) | essential | all data, RAG vectors, graph memory | always | postgres.enabled or global.postgresql.host |
— |
| valkey | essential | cache, rate limits, SSE fan-out, MCP registry | always | valkey.enabled or global.valkey.host |
— |
| keycloak | essential | sign-in, tenants, RBAC | always | keycloak.enabled or global.oidc.issuerUrl |
— |
| core-jobs | scale shape | scheduled sweeps and pollers out of the API pods (API adds shape-api) |
profile jobs |
global.components.coreJobs (default on) |
— |
| core-ingest | scale shape | pool of the jobs shape that runs only document ingestion and re-embedding | profile shape-jobs-ingest (core-jobs gets APP_SCHEDULING_OFFLOADED_POOLS) |
global.components.coreIngest (default off, needs coreJobs) |
— |
| core-webhooks | scale shape | pool of the jobs shape that runs only webhook delivery and the workflow event outbox relay | profile shape-jobs-pool, APP_SCHEDULING_POOL=webhooks |
global.components.coreWebhooks (default off, needs coreJobs) |
— |
| core-intake | scale shape | pool of the jobs shape that runs only the inbound email and X DM pollers | profile shape-jobs-pool, APP_SCHEDULING_POOL=intake |
global.components.coreIntake (default off, needs coreJobs) |
— |
| core-billing | scale shape | pool of the jobs shape that runs only the usage rollup, payment reconciliation and comp-grant sweeps | profile shape-jobs-pool, APP_SCHEDULING_POOL=billing |
global.components.coreBilling (default off, needs coreJobs) |
— |
| core-worker | scale shape | Temporal workflow worker | profile temporal |
global.temporal.enabled |
WORKFLOW_EXECUTION_ENGINE=temporal |
| migrate | scale shape | Flyway for every schema before the API starts | — (API migrates on boot) | core.migrate.enabled (hook Job) |
APP_TENANCY_MIGRATE_ON_BOOT=false on the API |
| mcp-stdio-runner | plug-and-play | stdio MCP servers (npx/uvx packages): tenant-registered ones and the built-in filesystem, memory, sequential-thinking |
profile tools (or sandbox) |
global.components.mcpStdioRunner + mcp-stdio-runner.runtimeClassName |
MCP_STDIO_SIDECAR_ENABLED=true + profile mcp |
| mcp-stdio-runner-browser | plug-and-play | the above plus the browser built-ins puppeteer and playwright-vision (pinned Chromium/Chrome baked in) |
profile browser (MCP_STDIO_RUNNER_VARIANT=-browser) |
mcp-stdio-runner.browser=true |
same as the runner |
| core-full image | alternative | stdio MCP servers inside the core container, for hosts that cannot run the runner (single-user only: no isolation) | CORE_IMAGE_VARIANT=-full + CORE_MCP_PROFILE=,mcp |
global.core.mcpToolchain=full (API only) |
MCP_STDIO_SIDECAR_ENABLED=false |
| sidecar | plug-and-play | document ingestion, embeddings, rerank, STT/TTS, rendering | always in full; lite profile rag |
global.components.sidecar (default on) |
SIDECAR_BASE_URL |
| tool-sandbox | plug-and-play | tenant-authored Java/Python tools | profile sandbox |
global.components.toolSandbox + RuntimeClass |
TOOL_SANDBOX_ENABLED=true |
| code-intel | plug-and-play | code intelligence, repository indexing | profile code-intel |
global.components.codeIntel |
CODE_INTEL_ENABLED=true |
| app-builder | plug-and-play | the App Builder (/build) |
profile builder |
global.components.appBuilder |
APP_BUILDER_CORE_SERVICE_TOKEN |
| voice-agent | plug-and-play | LiveKit voice calls with agents | profile voice |
global.components.voiceAgent + global.livekit.* |
LIVEKIT_* |
| rl-trainer | plug-and-play | offline reinforcement-learning training of agent policies | profile rl |
global.components.rlTrainer |
RL_TRAINER_INTERNAL_TOKEN |
How the mcp profile and the runner fit together:
MCP_STDIO_SIDECAR_ENABLED=true: core relays every stdio MCP server, the static built-ins included (StaticMcpStdioSidecarRouting, same connection names and exposure policy), to the runner through the trusted/opt/mcp-stdio-proxy/proxy.js.filesystem's root (MCP_FILESYSTEM_ROOT) then lives in the runner container. Browser tools work only when the browser image is behind the runner address.MCP_STDIO_SIDECAR_ENABLED=falsewith themcpprofile: core spawns the built-ins itself and needsnpxon PATH (thecore-fullimage). On the slim image core stops at startup with a message naming the three ways out: enable the runner, usecore-full, or dropmcp.- core-worker and core-jobs are published slim only. Give them MCP tools through the runner.
Required secrets¶
| Variable | What | Notes |
|---|---|---|
APP_ENCRYPTION_KEY |
AES-256-GCM key for stored provider keys, channel secrets, MCP env | Base64 of exactly 32 bytes (openssl rand -base64 32); core refuses to start otherwise. Must be identical for every core shape and every process that shares the database. Losing it makes the stored secrets unrecoverable: back it up with the database. |
APP_ENCRYPTION_KEY_PREVIOUS |
previous key during rotation | optional |
PLATFORM_MASTER_KEY |
platform-level secret material | generated by the CLI |
POSTGRES_USER / POSTGRES_PASSWORD (Compose .env) |
PostgreSQL bootstrap superuser | used only by the one-shot db-roles service, backups and manual psql; never handed to core |
POSTGRES_APP_USER / POSTGRES_APP_PASSWORD (Compose .env) |
the login core uses | default vegaduta_app; owner of POSTGRES_DB (agentic_ai) but not a superuser. Compose passes it to core as POSTGRES_USER/POSTGRES_PASSWORD. Equal to POSTGRES_USER = opt out (not recommended) |
KEYCLOAK_CLIENT_SECRET |
secret of the agentic-ai-core client |
must match the realm |
scripts/vegaduta secrets (and scripts\vegaduta.ps1 secrets) also keeps a copy of these values outside the install folder, readable only by you: ~/.config/vegaduta/secrets.env, or %LOCALAPPDATA%\VegaDuta\secrets.env on Windows (also used from WSL). A new install folder reuses them, so the data Docker kept from an earlier install still opens. If Docker holds a personal install's data but no copy exists, the new install leaves that data untouched and starts under its own compose project (VEGADUTA_PERSONAL_PROJECT, e.g. vegaduta-personal-2).
| Variable | What | Notes |
|---|---|---|
VEGADUTA_SECRETS_KEEP (environment) |
path of the kept copy | default above; tests point it at a temporary file |
VEGADUTA_PERSONAL_PROJECT (Compose .env) |
compose project of a personal install | set by secrets only in the case above; empty = COMPOSE_PROJECT_NAME, else vegaduta-personal |
Database and cache¶
| Property | Env | Default |
|---|---|---|
spring.datasource.url |
DB_HOST, DB_PORT, POSTGRES_DB |
localhost:5432/agentic_ai |
| Hikari pool | DB_HIKARI_MAX_POOL_SIZE, DB_HIKARI_MIN_IDLE, DB_HIKARI_CONNECTION_TIMEOUT_MS |
15, 5, 20000 |
app.tenancy.migrate-parallelism |
APP_TENANCY_MIGRATE_PARALLELISM |
4 tenant schemas migrated at once. Each Flyway run holds 2 pooled connections and the migrate Job holds 1 more for its advisory lock, so the value is capped to (pool size - 2) / 2 (a warning says when it is). The Helm migrate Job's pool is 10, enough for 4 |
spring.data.redis.* |
REDIS_HOST, REDIS_PORT, REDIS_PASSWORD |
localhost, 6379, empty (Valkey is compatible) |
spring.data.redis.timeout |
REDIS_TIMEOUT |
5s (Lite: 2s); core issues no blocking Redis commands |
Postgres must have the pgvector extension. Apache AGE (graph memory) is
optional: the ghcr.io/cd-lc/postgres image has both; on a server that does not offer AGE (AWS RDS/Aurora,
Cloud SQL, AlloyDB) core records the graph-memory migration as skipped and you drop the graph-memory profile
(Helm: global.postgresql.age=false). A server that offers AGE but refuses it (not allow-listed, permission denied)
still fails the migration, so that is fixed rather than hidden. Each tenant is a schema selected with
SET search_path, so a PgBouncer in front of it must use session pooling
(POOL_MODE=session).
Least-privileged login. Run core as a role that owns the database but is not a superuser (Compose does this for you, Helm's CloudNativePG cluster does it too). That role needs, and only needs:
- ownership of the database (it creates one schema per tenant and runs Flyway);
- the
vectorandageextensions created beforehand by a superuser (neither is a "trusted" extension); USAGEon schemaag_catalogandSELECT, INSERT, UPDATE, DELETEon its tables (graph memory);ageinshared_preload_libraries. A non-superuser may onlyLOADlibraries under$libdir/plugins/; theghcr.io/cd-lc/postgresimage linksage.sothere, soLOAD '$libdir/plugins/age'works, while a plainLOAD 'age'is refused.
For an external database, create it that way yourself
(deploy/postgres/ensure-app-role.sh is the reference, run it as the
superuser with PGHOST, PGUSER, PGDATABASE, APP_DB_USER,
APP_DB_PASSWORD set).
HTTP response compression and the JPA session. JSON API responses are gzip-compressed when the client accepts it:
server.compression.enabled (APP_SERVER_COMPRESSION_ENABLED, default true), for the types in
server.compression.mime-types (application/json,application/problem+json; the streamed text/event-stream chat
replies are never compressed, so a reply is not held back in a compression buffer), above
server.compression.min-response-size (APP_SERVER_COMPRESSION_MIN_SIZE, default 1024 bytes).
spring.jpa.open-in-view is false: a database session is not held open for a whole web request (and a whole
streamed reply), only for the work that needs it.
Database login without a stored password (IAM / token authentication)¶
app.datasource.auth (env DB_AUTH) selects how core logs in. The default,
password, is the behaviour above: POSTGRES_PASSWORD is required and Spring
Boot builds the pool. The other modes replace the password with a short-lived
token that is requested from the cloud for every new physical connection;
the same pool feeds the control-plane Flyway run, every per-tenant schema
migration, the migrate-only shape and JPA.
DB_AUTH |
Token | Credentials come from |
|---|---|---|
password (default) |
none | POSTGRES_PASSWORD |
aws-iam |
RDS / Aurora IAM auth token, valid 15 minutes (RdsUtilities) |
AWS SDK default chain: EKS IRSA, Pod Identity, instance profile. Region from AWS_REGION or DB_AWS_REGION |
azure-entra |
Entra access token for scope DB_AZURE_SCOPE (default https://ossrdbms-aad.database.windows.net/.default) |
DefaultAzureCredential: AKS Workload Identity, managed identity, az login |
gcp-iam |
OAuth2 access token for scope DB_GCP_SCOPE (default https://www.googleapis.com/auth/sqlservice.login) used as the password |
Application Default Credentials: GKE Workload Identity, metadata server |
Rules and behaviour:
POSTGRES_USERis the database role mapped to the cloud identity: an RDS user grantedrds_iam, an Entra principal created as a role, or the Cloud SQL IAM user (for a service account its email without.gserviceaccount.com).POSTGRES_PASSWORDis not needed and is ignored (a warning is logged if set).- TLS is mandatory. Core refuses to start unless
spring.datasource.urlwould make the pgjdbc driver encrypt:sslmode=require,verify-caorverify-full, orssl=true(which pgjdbc treats asverify-full). The check reads the URL the way the driver does (parameter names are case-sensitive, soSSLMODE=...does not count; the last occurrence wins) and a test compares it with the real driver. With Helm setglobal.postgresql.sslMode; otherwise setSPRING_DATASOURCE_URLyourself, for examplejdbc:postgresql://<host>:5432/<db>?options=-c%20TimeZone%3DUTC&sslmode=verify-full&sslrootcert=/certs/ca.pem. Be aware of what each value gives you:requireencrypts but does not authenticate the server, so an active man in the middle could capture the token (valid for minutes).verify-ca/verify-fullauthenticate it but need the provider's CA where the driver can find it (sslrootcert=<file>, or pgjdbc's default~/.postgresql/root.crt); the RDS, Azure and Cloud SQL CAs are not in that default location, and Cloud SQL server certificates do not match the instance host name (verify-caat most, or the Cloud SQL connector). The Helm chart only emitssslmode: it has no knob to mount a CA file, so supplying one means overridingSPRING_DATASOURCE_URLand mounting the file yourself. This CA handling has not been exercised against any real provider. - A token is cached and refreshed
DB_TOKEN_REFRESH_SKEW(default2m) before it expires. If a refresh fails while the cached token is still valid it is reused, a warning is logged, and the source is not asked again for 30 seconds (so an identity-provider outage does not make every new connection wait on a failing call); after the token has expired, opening a connection fails with SQLSTATE28000. Already-open pooled connections are not re-authenticated, so they keep working until the server or Hikari (maxLifetime) closes them. - The token modes connect core straight to the managed server. Do not put PgBouncer in front of them; the managed offerings have their own pooler (RDS Proxy, Azure built-in PgBouncer, Cloud SQL managed connection pooling), which must still be in session mode.
- An unknown
DB_AUTHvalue fails startup. Inpasswordmode a missing or blankPOSTGRES_PASSWORDfails startup with a clear message (it used to fail on the unresolved placeholder). This is enforced in code and tested with Spring Boot'sDataSourceAutoConfigurationloaded; it is skipped only when aJdbcConnectionDetailsbean other than Boot's own properties-backed one supplies the login (Testcontainers@ServiceConnection). - Compose: the shipped
deploy/compose/docker-compose.ymlroutes core through PgBouncer (DB_HOST: pgbouncer) and requiresPOSTGRES_APP_PASSWORD, and it sets noDB_AUTH. The token modes are therefore not wired into the shipped Compose files; they target a managed database from Kubernetes (or a Compose file of your own that points core at the managed server directly). - AWS credentials: IRSA (web identity) needs the AWS SDK
stsmodule, which core ships (a test fails if it disappears); EKS Pod Identity and instance profiles need nothing extra. - Helm:
global.postgresql.authsets it, requires an externalhostand asslModeofrequire,verify-caorverify-full, and drops the password Secret from the core pods. Put the cloud identity on the ServiceAccounts ofcore,core-jobsandcore-workerthrough<chart>.serviceAccount.annotations:eks.amazonaws.com/role-arn(IRSA),azure.workload.identity/client-id(plus the pod labelazure.workload.identity/use: "true"), oriam.gke.io/gcp-service-account. Keycloak's own database is separate and still logs in with a password. - Helm NetworkPolicies (on by default) block the paths a token login needs, so
with a token mode the chart adds egress rules for the core pods and the
migrate Job: HTTPS (443) to public addresses (AWS STS, Entra), plus one /32
rule for the credential endpoint that is link-local:
169.254.170.23:80(EKS Pod Identity agent) foraws-iam,169.254.169.254:80(GKE metadata server) forgcp-iam. The node's own metadata service is deliberately not opened on AWS or Azure (it would hand the node role to any pod). Not covered, add them withglobal.networkPolicy.extraEgress/global.networkPolicy.coreEgress.cidrsif you need them: STS or Entra reached through a VPC / private endpoint (private address), Pod Identity over IPv6, GKE Dataplane V1 / Calico metadata ports (169.254.169.252:988), and a database reached through a private address that is not covered byglobal.postgresql.port(the database port itself is opened automatically). The rendered rules are checked bydeploy/helm/test-guards.sh; that they are enough on a real cluster has not been tried. DB_AWS_REGION(blank = SDK chain),DB_AZURE_SCOPEandDB_GCP_SCOPEexist for sovereign clouds and unusual setups; leave them unset otherwise.
Status: the caching logic, the TLS guard (compared with the real pgjdbc
parser), the startup check and the wiring (with Spring Boot's
DataSourceAutoConfiguration loaded, through the pool, with the default AWS
token path) are unit-tested; the AWS token is produced by the real SDK signer
offline; the Helm guards and NetworkPolicy rules are rendered and asserted by
deploy/helm/test-guards.sh. The Azure and GCP token sources only wrap their
SDKs and have no test. Flyway and JPA are not started in these tests (they take
"the" DataSource by type; the test proves that is the token pool). No run
against a real RDS, Azure or Cloud SQL instance, with real workload-identity
credentials, or on a cluster with the NetworkPolicies enforced has been done.
All three cloud SDKs are on the classpath of every core shape, including
password-mode images.
Authentication (OIDC and Keycloak)¶
This section is the Keycloak (app.auth.mode=keycloak, default) setup. For the single-owner alternative that needs no identity server, see Local sign-in.
| Property | Env | Default |
|---|---|---|
spring.security.oauth2.resourceserver.jwt.issuer-uri |
KEYCLOAK_ISSUER_URI |
none: required under the security profile when app.auth.mode=keycloak (the default); core refuses to start without it, with a message naming this variable (never a localhost default). Compose and Helm derive it from PUBLIC_AUTH_URL (<PUBLIC_AUTH_URL>/realms/<KEYCLOAK_REALM>). With app.auth.mode=local it is not needed; the personal profile fills in a URN that names no issuer |
app.keycloak.jwk-set-uri |
KEYCLOAK_JWK_SET_URI |
<issuer>/protocol/openid-connect/certs |
app.keycloak.additional-issuers |
KEYCLOAK_ADDITIONAL_ISSUERS |
empty; comma-separated. Compose also passes it to code-intel, app-builder and rl-trainer (CODE_INTEL_/APP_BUILDER_/RL_TRAINER_KEYCLOAK_ADDITIONAL_ISSUERS), which validate iss against the same list; their *_KEYCLOAK_ISSUER has no default and must be set under *_AUTH_MODE=keycloak |
app.keycloak.require-email-verification |
KEYCLOAK_REQUIRE_EMAIL_VERIFICATION (lite) |
true; the lite Compose tier sets false because it has no SMTP. A new user gets the VERIFY_EMAIL required action only when true; with no mail path that action ends the first sign-up on "Failed to send email". |
app.keycloak.admin-base-url, app.keycloak.realm |
KEYCLOAK_ADMIN_BASE_URL, KEYCLOAK_REALM |
http://localhost:8180, agentic-ai |
app.keycloak.client-id / client-secret |
KEYCLOAK_CLIENT_ID / KEYCLOAK_CLIENT_SECRET |
agentic-ai-core |
app.security.oidc.roles-claim-path |
OIDC_ROLES_CLAIM_PATH |
realm_access.roles — dot-path or literal claim name carrying role/authority strings; a flat namespaced key works too (e.g. https://example.com/roles for Auth0) |
app.security.oidc.tenant-claim |
OIDC_TENANT_CLAIM |
tenant_id — the token claim that resolves the tenant; a missing claim fails closed |
app.security.oidc.scopes-claim |
OIDC_SCOPES_CLAIM |
empty (off) — claim carrying OAuth scopes as a space-delimited string or list; emitted as additional SCOPE_* authorities only, never a substitute for stored PERM_* grants |
app.identity.provider |
IDENTITY_PROVIDER |
keycloak — selects the IdentityUserAdminPort bean: keycloak → KeycloakAdminClient, auth0 → Auth0UserAdminClient. Exactly one is created; per-tenant SSO (/api/tenant/sso, incl. SCIM token minting) and /api/auth/social-providers are Keycloak-only and 404 under auth0 |
app.identity.auth0.domain |
AUTH0_DOMAIN |
none; required when IDENTITY_PROVIDER=auth0. Bare host or https:// URL of the Auth0 tenant |
app.identity.auth0.client-id / client-secret |
AUTH0_CLIENT_ID / AUTH0_CLIENT_SECRET |
none; a Machine-to-Machine application authorized against the Management API. Scopes: read:users, create:users, update:users, delete:users, read:roles, delete:sessions, delete:refresh_tokens; it must also have AUTH0_CONNECTION enabled (Auth0's emailed password reset refuses unlinked clients) |
app.identity.auth0.connection |
AUTH0_CONNECTION |
Username-Password-Authentication — the database connection users are created on |
app.identity.auth0.audience |
AUTH0_AUDIENCE |
https://<AUTH0_DOMAIN>/api/v2/ — Management API audience for the client_credentials grant |
Non-Keycloak issuers (Auth0, Microsoft Entra, Okta): set KEYCLOAK_ISSUER_URI
and KEYCLOAK_JWK_SET_URI to the provider's endpoints (jwk-set-uri has a
Keycloak-style default), map claims with the app.security.oidc.* properties
above, and set APP_KEYCLOAK_ALLOWED_AZP (its default lists Keycloak's
first-party client names) or empty it to disable the azp check. For Auth0,
also set IDENTITY_PROVIDER=auth0 and the AUTH0_* variables so the
user-admin surface works — without them the console can validate Auth0
tokens but cannot list, create or delete users. Auth0 users must carry
app_metadata.tenant_id and a roles claim mapped via OIDC_ROLES_CLAIM_PATH
(e.g. a namespaced https://<ns>/roles emitted by an Auth0 Action).
Keycloak itself (Compose). Full runs Keycloak in production mode
(start) on its own PostgreSQL; the domain overlay always does, and
KEYCLOAK_START_COMMAND=start-dev is refused there. Lite uses start-dev
with an embedded store, for a single machine only. The realm import
(deploy/keycloak/agentic-ai-realm.json, first start only) has no fallback
client secret (KEYCLOAK_CLIENT_SECRET is required), redirect URIs and web
origins only from PUBLIC_APP_URL/PUBLIC_ADMIN_URL, PKCE S256 on every
public client, and the password grant off on the core client
(KC_CORE_DIRECT_GRANTS=true turns it on, for CI logins only). core's service
account gets view-users, query-users, manage-users, query-groups,
view-realm, view-identity-providers and manage-identity-providers of
realm-management, not realm-admin; scripts/vegaduta up removes a
realm-admin grant an older install made. Sign-up e-mail verification follows
KC_VERIFY_EMAIL (off by default because it needs SMTP; doctor warns on a
domain install).
Dual issuer. The iss claim in a token is the URL the browser used to
reach Keycloak (for example https://auth.example.com/realms/agentic-ai), while
core often fetches keys over an internal address (http://keycloak:8080). Core
accepts the primary issuer plus every entry in KEYCLOAK_ADDITIONAL_ISSUERS.
If the public auth URL is in neither, every request fails with 401. Keep
the primary issuer and add the public one; don't replace the primary. The
JWK URL may stay internal.
Browser URLs. The consoles read NEXT_PUBLIC_API_BASE_URL,
NEXT_PUBLIC_KEYCLOAK_URL, NEXT_PUBLIC_KEYCLOAK_REALM and
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID at runtime, with the value baked in at build
time as fallback. Compose derives them from PUBLIC_API_URL, PUBLIC_AUTH_URL
and PUBLIC_APP_URL. On a server, none of these may be localhost. Core's
CORS list is app.web.allowed-origins (APP_WEB_ALLOWED_ORIGINS), which must
contain the console origins.
| Property | Env | Default |
|---|---|---|
app.web.public-url |
APP_WEB_PUBLIC_URL (Compose sets it from PUBLIC_APP_URL; PUBLIC_APP_URL alone also works) |
none. The one browser-facing origin of the tenant console that share links, e-mail links and OAuth console redirects are built on. Blank fails start-up under the security profile and logs a warning otherwise; there is no localhost default |
app.google.oauth.console-redirect-uri |
GOOGLE_OAUTH_CONSOLE_REDIRECT_URI |
blank = <app.web.public-url>/admin/integrations |
app.x.oauth.console-redirect-uri |
X_OAUTH_CONSOLE_REDIRECT_URI |
blank = <app.web.public-url>/admin/channels |
app.atlassian.oauth.console-redirect-uri |
ATLASSIAN_OAUTH_CONSOLE_REDIRECT_URI |
blank = <app.web.public-url>/tools |
app.newsletter.unsubscribe-mailto |
(property only) | blank = the mail sender address (spring.mail.username). The mailto: target of the List-Unsubscribe header on platform-admin newsletters |
Encryption key management (cloud KMS)¶
app.crypto.kms.provider chooses the key that wraps the per-secret data keys of envelope encryption
(EnvelopeEncryptionService). Today that path protects the payment mandate signing keys only. About 41 other
classes (provider keys, channel secrets, MCP env) still use EncryptionService and the static APP_ENCRYPTION_KEY;
they are not moved to a KMS yet. APP_ENCRYPTION_KEY therefore stays required in every case.
| Property | Env | Default / notes |
|---|---|---|
app.crypto.kms.provider |
APP_CRYPTO_KMS_PROVIDER |
local (default, unchanged behaviour: APP_ENCRYPTION_KEY wraps the data keys) | aws | azure | gcp. Case-insensitive; an empty value means local. Anything else, or a cloud value without a key id, fails startup |
app.crypto.kms.aws.key-id / region |
APP_CRYPTO_KMS_AWS_KEY_ID / APP_CRYPTO_KMS_AWS_REGION |
key ARN (preferred; an alias works but is not stable: re-pointing it makes Decrypt fail, and the provider id is the string you configured, so swapping alias and ARN reads as a different key); region blank = SDK default chain. Credentials: AWS default chain (IRSA / Pod Identity / instance role) |
app.crypto.kms.azure.key-id / algorithm |
APP_CRYPTO_KMS_AZURE_KEY_ID / APP_CRYPTO_KMS_AZURE_ALGORITHM |
key URL https://<vault>.vault.azure.net/keys/<name> (no version = newest wraps; a pinned version wraps with that version and gives the same provider id; the version and the wrap algorithm used are stored with each wrapped key, so rotating or changing the algorithm does not strand data); algorithm default RSA-OAEP-256, must be a known Key Vault wrap algorithm or startup fails. Credentials: DefaultAzureCredential (workload identity, managed identity) |
app.crypto.kms.gcp.key-name / endpoint |
APP_CRYPTO_KMS_GCP_KEY_NAME / APP_CRYPTO_KMS_GCP_ENDPOINT |
projects/P/locations/L/keyRings/R/cryptoKeys/K (symmetric, no version); endpoint default https://cloudkms.googleapis.com, must be https (the token and data key go to it). Credentials: Application Default Credentials (GKE Workload Identity) |
app.crypto.kms.gcp.allow-insecure-test-endpoint |
APP_CRYPTO_KMS_GCP_ALLOW_INSECURE_TEST_ENDPOINT |
false. Only for tests against a local fake: lets endpoint be plain http to localhost/127.0.0.1/[::1]. Never set it in a deployment |
Permissions the workload identity needs (on that one key):
| Cloud | Needed |
|---|---|
| AWS | kms:GenerateDataKey and kms:Decrypt (the calls carry the encryption context purpose=vegaduta-envelope) |
| Azure | key permissions wrapKey and unwrapKey (RBAC role "Key Vault Crypto User"); an RSA key |
| GCP | cloudkms.cryptoKeyVersions.useToEncrypt and useToDecrypt (role "Cloud KMS CryptoKey Encrypter/Decrypter") |
Behaviour to know:
- Every envelope records the provider id (
aws:<key id>,azure:<key url>,gcp:<key name>, orlocal). Decrypting an envelope whose provider/key is not registered fails with a message naming both ids; a wrong key is never tried. - Switching
localto a cloud provider is safe for existing data: the local provider stays registered, so envelopes written before the switch keep decrypting and new ones use the cloud key. Switching away from a cloud provider, or changing the key id, strands what that key wrapped. Rotate inside the KMS (AWS and GCP key rotation keep old versions usable) rather than by changing the id. - Set the same values on every core shape (api, jobs, worker).
- Startup versus first use differs by cloud. GCP resolves Application Default Credentials in the constructor, so
missing credentials fail boot. AWS builds its client at startup but resolves credentials on the first call, so a
bad role shows up on the first wrap or unwrap. Azure builds each
CryptographyClient(one per key identifier, sharing one credential and HTTP client) on first use, so a bad identity or vault URL also shows up there. - Availability: the payment signing key is decrypted on every mandate signing, so under a cloud provider each signature makes one KMS / Key Vault / Cloud KMS call. Their availability, latency and quotas are on that path.
- Integrity binding differs: AWS binds the encryption context and GCP binds additional authenticated data
(
purpose=vegaduta-envelope); AzurewrapKeyhas no such parameter. GCP responses are not CRC32C-checked. - Helm:
global.core.extraEnv(for exampleAPP_CRYPTO_KMS_PROVIDER: aws), plus the cloud's workload identity annotations throughserviceAccount.annotationsof thecore,core-jobsandcore-workercharts. That wiring is the existing chart mechanism; it was not rendered or run with these settings.
File storage¶
| Property | Env | Default |
|---|---|---|
app.storage.type |
APP_STORAGE_TYPE |
s3 (Lite profile: local) |
app.storage.local.path |
APP_STORAGE_LOCAL_PATH |
/data/storage in Lite; required when type=local |
app.storage.endpoint |
STORAGE_ENDPOINT |
http://localhost:9000 (Full: SeaweedFS) |
app.storage.region / bucket |
STORAGE_REGION / STORAGE_BUCKET |
us-east-1 / agentic-ai |
app.storage.access-key / secret-key |
STORAGE_ACCESS_KEY / STORAGE_SECRET_KEY |
set real values; the built-in defaults are for local development only. Read only with type=s3 and credentials=static (the MinIO-default WARN is skipped otherwise) |
spring.data.redis.ssl.enabled / username |
REDIS_SSL / REDIS_USERNAME |
TLS and ACL user for managed Redis-protocol services (ElastiCache, Azure Cache for Redis, Memorystore); defaults keep the plain connection and default user |
spring.kafka.security.protocol, spring.kafka.properties.sasl.* |
SPRING_KAFKA_SECURITY_PROTOCOL, SPRING_KAFKA_PROPERTIES_SASL_MECHANISM, SPRING_KAFKA_PROPERTIES_SASL_JAAS_CONFIG |
managed Kafka (Event Hubs: SASL_SSL + PLAIN; MSK SCRAM: SASL_SSL + SCRAM-SHA-512); the JAAS line carries the credential, keep it in a Secret. MSK IAM auth needs a client library that is not in the image |
app.storage.type=azure-blob, app.storage.azure.endpoint / connection-string |
APP_STORAGE_TYPE, STORAGE_AZURE_ENDPOINT, STORAGE_AZURE_CONNECTION_STRING |
Azure Blob Storage (Azure has no S3 API); the container is STORAGE_BUCKET. With STORAGE_CREDENTIALS=default it uses DefaultAzureCredential (AKS workload identity, managed identity) against the endpoint and stores no key; with static it uses the connection string. No demo-image expiry on Azure: S3 gets a bucket lifecycle rule that expires demo-images/ objects after app.demo.lifetime-days; Azure Blob lifecycle management is an account-level policy on the ARM management plane, which the data-plane SDK (and the application identity) cannot set. Create it yourself on the storage account: a delete action on base blobs daysAfterModificationGreaterThan: <lifetime-days> with prefix <container>/demo-images/ (az storage account management-policy create). Until then those objects are kept. A missing container is an error, as a missing S3 bucket is, not a "not found" |
app.storage.credentials |
STORAGE_CREDENTIALS |
static (default: the key pair above) or default: the AWS SDK credential chain with no keys (EKS IRSA / Pod Identity, ECS or EC2 role, AWS_* env). For AWS S3 (needs the sts module for IRSA, which core ships); with app.storage.type=azure-blob it selects DefaultAzureCredential. GCS needs HMAC keys (static). LiveKit call recording uploads with the key pair above, so it needs type=s3 with static; with default or azure-blob a recording request fails immediately with a message naming this. In Kubernetes only token-file identities (IRSA, Azure workload identity) work under the chart's default NetworkPolicy; see "Cloud identity for object storage" in deploy/helm/README.md |
app.storage.path-style-access |
STORAGE_PATH_STYLE_ACCESS |
true |
local works for a single core process. Anything with more than one core
replica (Full with jobs, Kubernetes) needs S3.
Embeddings and knowledge¶
| Property | Env | Default |
|---|---|---|
app.embeddings.provider |
APP_EMBEDDINGS_PROVIDER |
sidecar; or openai-compatible; or ollama (free local embeddings; a private or loopback Ollama host must be listed in APP_SECURITY_OUTBOUND_ALLOWED_PRIVATE_HOSTS, e.g. ollama:11434); or auto: a cloud key (app.embeddings.api-key with base-url) wins, else a configured Ollama (app.embeddings.ollama.base-url, or the local-LLM bootstrap Ollama), else an Ollama registered at run time (a platform-pool row pointing at the model server the local-model wizard manages; read per call, cached 30 s), else the sidecar |
app.embeddings.base-url / api-key / model |
APP_EMBEDDINGS_BASE_URL / APP_EMBEDDINGS_API_KEY / APP_EMBEDDINGS_MODEL |
for openai-compatible (OpenAI, Ollama, vLLM, LiteLLM, ...) |
app.embeddings.dimensions |
APP_EMBEDDINGS_DIMENSIONS |
384 (must be 1..384) |
app.sidecar.base-url |
SIDECAR_BASE_URL |
http://localhost:8001; extraction, transcription, reranking |
app.sidecar.enabled |
APP_SIDECAR_ENABLED |
true. false makes the sidecar optional: no boot dependency, rerank fails open, html/htm/docx are extracted in process, and PDF/OCR, transcription, TTS/captions/render and sidecar embeddings answer 503 naming this setting. The health surfaces report it as not configured, not down |
app.embeddings.ollama.base-url |
APP_EMBEDDINGS_OLLAMA_BASE_URL |
blank: falls back to the local-LLM bootstrap Ollama (app.local-llm.bootstrap.base-url) |
app.embeddings.ollama.model |
APP_EMBEDDINGS_OLLAMA_MODEL |
nomic-embed-text. Known: nomic-embed-text (768), mxbai-embed-large (1024), bge-m3 (1024), all-minilm (384), snowflake-arctic-embed, qwen3-embedding, embeddinggemma (768). An unknown model's width is learned from its first response |
app.embeddings.ollama.dimensions |
APP_EMBEDDINGS_OLLAMA_DIMENSIONS |
0: take the width from the model table, else learn it |
app.embeddings.ollama.allow-truncation |
(none) | unset: follow the model table. true allows cutting and re-normalising a vector wider than the pgvector column (768 knowledge, 384 memory and cache) for a model that is not Matryoshka-trained |
app.embeddings.ollama.query-prefix / document-prefix |
(none) | unset: the model's default prefix; an empty string means none |
app.embeddings.ollama.keep-alive |
APP_EMBEDDINGS_OLLAMA_KEEP_ALIVE |
10m |
app.knowledge.embedding-binding.enabled |
KNOWLEDGE_EMBEDDING_BINDING_ENABLED |
true. Each knowledge collection records which embedder wrote it (sidecar or provider:model:width, migration V550). Ingesting with a different one answers 409 naming POST /api/knowledge/collections/{id}/reembed, and vector search skips the mismatched collection (keyword search still answers). false turns the guard off |
app.knowledge.rerank.enabled |
KNOWLEDGE_RERANK_ENABLED |
false |
Changing the embedding provider on a populated database requires re-embedding every knowledge collection.
LLM providers and keys¶
There are no LLM API keys in core's configuration. Tenants add provider
keys in the console (bring your own key); they are stored encrypted with
APP_ENCRYPTION_KEY. Supported providers are listed in
core/providers/.../ProviderName.java.
| Property | Env | Default | |
|---|---|---|---|
app.agents.default-model |
DEFAULT_MODEL |
claude-sonnet-4-5 |
model preselected for new agents |
| hosted pool ordering | HOSTED_POOL_ORDER_BY_COST |
true |
platform-operated shared pool (optional), configured by a platform admin |
app.llm.default-source |
(none) | auto |
which source a tenant that has made no explicit choice uses: platform-pool, byok, or auto (the pool when one is configured, otherwise the tenant's own key) |
app.free-tier.monthly-token-limit |
(none) | 400000 |
rolling-30-day token quota per FREE tenant on the pool (PREMIUM ×2, UNLIMITED ×3); editable per deployment in Platform Admin |
app.free-tier.meter-zero-cost-rows |
(none) | false |
see Zero-cost pool rows below |
| Vertex AI pool | VERTEX_ENABLED, VERTEX_POOL_* |
false |
|
app.platform-llm.warm.enabled |
APP_PLATFORM_LLM_WARM_ENABLED |
true |
when someone signs in or opens a chat, the console calls POST /api/platform-llm/warm, and core sends the pool's top row a one-token request so a model that scaled to zero is loaded before the first message |
app.platform-llm.warm.interval-seconds |
APP_PLATFORM_LLM_WARM_INTERVAL_SECONDS |
240 |
at most one warm-up per core instance in this window, however many people sign in |
Zero-cost pool rows¶
A pool row the operator runs themselves (the Ollama container that --local-ai starts, a vLLM or
llama.cpp server on the LAN, any OpenAI-compatible endpoint nobody bills per token) is marked
Free (self-hosted) in Platform Admin (zero_cost, migration V484). Such a row is priced at
$0 in llm_call_log and in the per-user daily dollar budget (an unknown model id would otherwise be
charged at the conservative fallback rate of $0.50 in / $2.00 out per MTok), is tried first in the
failover order, and when every enabled row is zero-cost the monthly token quota is not applied
either: a self-hosted install never shows "Free plan monthly token limit reached". Set
app.free-tier.meter-zero-cost-rows=true to keep the token quota for such a pool (one small shared
GPU still needs a ceiling). The bootstrap below creates its row as zero-cost; a mixed pool (paid and
free rows) still reserves tokens and dollars up front and refunds the zero-cost turns at the end.
ANTHROPIC_API_KEY is read only by the evaluation runner (core/quality, eval-harness.md) and by
optional services (app builder, RL trainer), not by core's chat path.
Provider endpoints, model defaults, retrieval and learning tuning¶
Work package 3d-2. These values were constants in the code; each is now a typed property (a class in the module
named in the first column) with the same default in application.yml, so an unset key behaves as before.
ModelRetrievalLearningDefaultsTest (core/app) binds the real application.yml and checks it equals the code defaults.
Vendor endpoints (ProviderEndpoints, core/providers). One base URL per vendor, read by the key validators, the
live model-list fetcher, the model catalog's OpenRouter fetch and the hosted pool's "pick a provider, paste a key" flow.
Point a vendor at a corporate proxy, a regional endpoint or a mock server with one key. The value must be an http(s) URL
(anything else fails startup); a blank value falls back to the default; a trailing slash is removed. Write the base URL the
way the vendor's chat-completions client appends /v1/chat/completions (Groq https://api.groq.com/openai, OpenRouter
https://openrouter.ai/api), never with a trailing /v1. The chat-client factories in core/chat still carry their own
literal and are not yet switched to ProviderEndpoints.baseUrl(...) (listed in the change log entry for this change).
| Property | Env | Default |
|---|---|---|
app.llm.endpoints.anthropic.base-url |
APP_LLM_ENDPOINTS_ANTHROPIC_BASE_URL |
https://api.anthropic.com |
app.llm.endpoints.openai.base-url |
APP_LLM_ENDPOINTS_OPENAI_BASE_URL |
https://api.openai.com |
app.llm.endpoints.grok.base-url |
APP_LLM_ENDPOINTS_GROK_BASE_URL |
https://api.x.ai |
app.llm.endpoints.gemini.base-url |
APP_LLM_ENDPOINTS_GEMINI_BASE_URL |
https://generativelanguage.googleapis.com |
app.llm.endpoints.mistral.base-url |
APP_LLM_ENDPOINTS_MISTRAL_BASE_URL |
https://api.mistral.ai |
app.llm.endpoints.deepseek.base-url |
APP_LLM_ENDPOINTS_DEEPSEEK_BASE_URL |
https://api.deepseek.com |
app.llm.endpoints.groq.base-url |
APP_LLM_ENDPOINTS_GROQ_BASE_URL |
https://api.groq.com/openai |
app.llm.endpoints.meta.base-url |
APP_LLM_ENDPOINTS_META_BASE_URL |
https://api.meta.ai |
app.llm.endpoints.openrouter.base-url |
APP_LLM_ENDPOINTS_OPENROUTER_BASE_URL |
https://openrouter.ai/api |
app.llm.endpoints.sarvam.base-url |
APP_LLM_ENDPOINTS_SARVAM_BASE_URL |
https://api.sarvam.ai |
app.llm.endpoints.<vendor>.models-path |
(none) | /v1/models (Gemini /v1beta/models) |
app.llm.endpoints.gemini.completions-path |
(none) | /v1beta/openai/chat/completions (hosted-pool default; other vendors use /v1/chat/completions) |
app.llm.endpoints.grok-language-models-path |
(none) | /v1/language-models |
app.llm.endpoints.openrouter-auth-key-path |
(none) | /v1/auth/key |
Model defaults (ProviderModelDefaultsProperties, core/providers). The maps are merged with the built-ins
(set one key to change one entry; a blank value withdraws it). A key with a dot is written "[grok-4.20]": ... in YAML.
| Property | Default | Was |
|---|---|---|
app.models.defaults.<provider> |
anthropic claude-haiku-4-5-20251001, openai gpt-4o-mini, gemini gemini-2.5-flash, grok grok-4-1-fast-non-reasoning, sarvam sarvam-30b, mistral mistral-small-latest, deepseek deepseek-v4-flash, groq llama-3.1-8b-instant, meta muse-spark-1.3, ollama qwen3.5:4b |
ProviderModelCatalogService.PREFERRED_DEFAULT_MODELS |
app.models.retired.<model-id> |
grok-3 to grok-4.3, grok-3-mini to grok-4.20-0309-non-reasoning, grok-4 to grok-4.5, grok-4-fast to grok-4.3, deepseek-chat to deepseek-v4-flash, deepseek-reasoner to deepseek-v4-pro, gemma2-9b-it to llama-3.1-8b-instant |
ProviderModelCatalogService.RETIRED_MODEL_REPLACEMENTS |
app.models.context-windows.<model-id> |
empty (the catalog value stands) | new: replaces the catalog's window for that model in the model list |
app.models.default-preference-keywords |
flash-lite,flash,mini,haiku,lite,8b,instant,non-reasoning |
ProviderModelCatalogService.DEFAULT_MODEL_PREFERENCE_KEYWORDS |
app.models.hosted-pool-auto-keywords |
flash-lite,flash,mini,haiku,lite,8b,instant |
HostedPoolRequestResolver.AUTO_MODEL_PREFERENCE_KEYWORDS |
Knowledge retrieval pool (KnowledgeSearchProperties, core/knowledge). How many chunks a search returns is the caller's
top-k (the agent's retrieval setting). The pool is min(candidate-pool-max, max(candidate-pool-min, top-k * candidate-pool-multiplier)).
| Property | Env | Default | Was |
|---|---|---|---|
app.knowledge.search.rrf-k |
APP_KNOWLEDGE_SEARCH_RRF_K |
60 |
KnowledgeSearchService.RRF_K |
app.knowledge.search.candidate-pool-min |
APP_KNOWLEDGE_SEARCH_CANDIDATE_POOL_MIN |
20 |
MIN_CANDIDATE_POOL |
app.knowledge.search.candidate-pool-max |
APP_KNOWLEDGE_SEARCH_CANDIDATE_POOL_MAX |
50 |
MAX_CANDIDATE_POOL |
app.knowledge.search.candidate-pool-multiplier |
APP_KNOWLEDGE_SEARCH_CANDIDATE_POOL_MULTIPLIER |
4 |
CANDIDATE_POOL_MULTIPLIER |
Graph memory limits (GraphMemoryProperties, core/graphmemory; only with the graph-memory profile).
| Property | Env | Default | Was |
|---|---|---|---|
app.graph-memory.recall.max-facts |
GRAPH_MEMORY_RECALL_MAX_FACTS |
20 |
GraphMemoryService.MAX_RECALL_FACTS |
app.graph-memory.recall.max-paths |
GRAPH_MEMORY_RECALL_MAX_PATHS |
200 |
GraphMemoryService.MAX_RECALL_PATHS |
app.graph-memory.arbitration.max-context-facts |
GRAPH_MEMORY_ARBITRATION_MAX_CONTEXT_FACTS |
30 |
MemoryWriteArbitrationService.MAX_CONTEXT_FACTS |
app.graph-memory.forget.scan-page |
GRAPH_MEMORY_FORGET_SCAN_PAGE |
500 |
GraphMemoryService.FORGET_SCAN_PAGE |
app.graph-memory.forget.scan-max-pages |
GRAPH_MEMORY_FORGET_SCAN_MAX_PAGES |
20 |
GraphMemoryService.FORGET_SCAN_MAX_PAGES |
Reinforcement learning (RlPolicyProperties, RlRewardProperties, RlFeedbackProperties, core/rl). The rl.policy.* and
rl.reward.* keys were bound but absent from application.yml (the module table below lists them with empty defaults); their
effective defaults are the ones in this table and now in the yml. Seeding keys apply when a policy is created; existing policies keep their rows.
| Property | Default | Was |
|---|---|---|
rl.policy.default-exploration-rate |
0.1 |
PolicyStore.DEFAULT_EXPLORATION_RATE |
rl.policy.reasoning-depth-arms |
FAST,CAREFUL,RIGOROUS |
arms hardcoded in PolicyStore.ensureDefaultArms |
rl.policy.gen-param-presets.<name>.temperature / .top-p |
PRECISE 0.2/0.8, BALANCED 0.7/0.9, CREATIVE 1.0/0.95 (a configured set replaces all three) |
arms hardcoded in PolicyStore.ensureDefaultArms |
rl.policy.ucb1-exploration-constant |
2.0 |
literal 2.0 in ContextualBanditService.selectByUcb1 |
rl.reward.clip-min / clip-max / safety-veto-cap |
0.0 / 1.0 / 0.1 |
unchanged (now also in the yml) |
rl.reward.human-weight / judge-weight / task-success-weight / cost-weight / latency-weight |
1.0 / 0.6 / 1.0 / 0.2 / 0.2 |
unchanged (now also in the yml) |
rl.feedback.thumbs-up-reward / thumbs-down-reward |
1.0 / 0.0 |
FeedbackService.signalToReward |
rl.feedback.resolved-reward / unresolved-reward / edit-reward |
1.0 / 0.0 / 0.0 |
FeedbackService.signalToReward |
rl.feedback.default-rating |
3 (stars assumed for a rating signal without one) |
literal in FeedbackService |
rl.feedback.export-desirable-min-rating / export-undesirable-max-rating |
4 / 2 |
RlExportService.RATING_DESIRABLE_MIN / RATING_UNDESIRABLE_MAX |
Feature flags for optional services¶
| Env | Default | Enables | Also set |
|---|---|---|---|
CODE_INTEL_ENABLED |
false |
code intelligence (services/code-intel) |
CODE_INTEL_BASE_URL, CODE_INTEL_INTERNAL_TOKEN |
TOOL_SANDBOX_ENABLED |
false |
custom Java/Python tools in services/tool-sandbox |
TOOL_SANDBOX_BASE_URL, TOOL_SANDBOX_INTERNAL_TOKEN |
MCP_STDIO_SIDECAR_ENABLED |
false |
run stdio MCP servers (tenant-registered and the mcp profile's built-ins) in services/mcp-stdio-runner instead of inside core |
MCP_STDIO_SIDECAR_URL, MCP_STDIO_SIDECAR_SHARED_SECRET |
MCP_STDIO_PREFLIGHT_FAIL_FAST |
true |
stop core at startup when a stdio launcher it needs is missing (npx for the mcp profile without the runner, node for the runner relay); false only logs a warning |
|
WORKFLOW_EVENTS_ENABLED |
false (true with the workflow profile) |
workflow events on Kafka | WORKFLOW_KAFKA_BOOTSTRAP_SERVERS |
WORKFLOW_KAFKA_ENABLED |
unset: true if a bootstrap server resolved, else false |
false keeps the workflow profile but starts no Kafka topic admin, outbox relay or consumers, and makes WORKFLOW_EVENTS_ENABLED default to false (an explicit value still wins). When unset, core decides at start-up: Kafka is on exactly when spring.kafka.bootstrap-servers is non-blank, whatever supplied it (WORKFLOW_KAFKA_BOOTSTRAP_SERVERS, SPRING_KAFKA_BOOTSTRAP_SERVERS, a key in the app Secret, extraEnvFromSecrets, envFrom, ...); with none it starts no Kafka client and logs one INFO line. The Helm chart never sets this flag (it cannot read Secret contents), so a Secret-held bootstrap is honoured. Only the literal false turns it off. The one-shot migrate process always runs with it off. There is no longer an implicit localhost:9092 default: set WORKFLOW_KAFKA_BOOTSTRAP_SERVERS (Compose and core/.env.example do). |
WORKFLOW_KAFKA_BOOTSTRAP_SERVERS |
KNOWLEDGE_RERANK_ENABLED |
false |
reranking via the sidecar | |
GRAPH_MEMORY_ARBITRATION_ENABLED |
false |
graph-memory conflict arbitration | |
APP_A2A_ENABLED |
true |
the inbound A2A surface: agent cards, /api/a2a JSON-RPC and REST. false turns the whole surface off |
|
APP_A2A_SIGNING_ENABLED |
true |
Ed25519-signed A2A agent cards | |
TOOL_AUTO_SELECTION_ENABLED |
true |
automatic tool selection per turn | |
APP_VISION_ENABLED |
true |
image inputs in chat | |
AGENT_JOB_TRIGGERS_ENABLED |
true |
scheduled agent jobs | |
SIGNUP_DISPOSABLE_EMAIL_BLOCK_ENABLED |
true |
block disposable email domains at signup | |
NEXT_PUBLIC_EDITION (web; Compose: VEGADUTA_EDITION) |
derived: personal when NEXT_PUBLIC_AUTH_MODE=local, else NEXT_PUBLIC_DEPLOY_ENV when that is lite or full, else every page on |
personal | lite | full: which pages the console has. On personal the rail, deep links and the proxy (a real 404) leave out the pages for features core does not run; lite and full hide nothing. Read at runtime, so an old image without it behaves as before. The console compares it with the edition core reports on GET /api/public/status and warns on a mismatch (see Edition tier and features) |
|
NEXT_PUBLIC_BILLING_ENABLED (web; Compose: WEB_BILLING_ENABLED) |
off | plans, prices, upgrade prompts and billing pages; off redirects /billing, /features and /admin/billing and hides every upgrade CTA |
1 to turn on (runtime, see web/docs/RUNTIME-CONFIG.md) |
NEXT_PUBLIC_SITE_MODE (web; Compose: WEB_SITE_MODE) |
oss |
which signed-out pages are served: oss is the technical open-source landing and About, and redirects the pricing, enterprise, hire, earn, compare and similar sales pages to /; classic restores the original marketing pages (tag backup/classic-marketing-pages-2026-10-12) |
oss or classic (runtime) |
NEXT_PUBLIC_DOCS_URL (web; Compose: WEB_DOCS_URL) |
empty | absolute URL of the published documentation site; when set, the public pages' "Docs" links and the in-app help banner point there, otherwise at the in-app /docs help (site modes) |
a URL (runtime) |
NEXT_PUBLIC_ACADEMY_URL (web) |
https://academy.vegaduta.ai |
origin of the separate (proprietary) Academy learning portal; compose's WEB_ACADEMY_URL defaults to the hosted Academy — point it at a self-hosted Academy, or empty in a fork that does not want the links |
a URL (runtime) |
NEXT_PUBLIC_CONTACT_EMAIL (web; Compose: WEB_CONTACT_EMAIL; Helm: web.env.NEXT_PUBLIC_CONTACT_EMAIL) |
empty | contact address on /plugins and /account-deletion. Empty shows a placeholder; set an address you own |
an e-mail address (runtime) |
NEXT_PUBLIC_CODE_INTEL_URL (web; Compose: PUBLIC_CODE_INTEL_URL) |
empty | browser-facing code-intel URL. Leave empty so the web app uses the core proxy; a localhost value is baked into the browser bundle and fails for every non-local viewer. Helm sets it from global.hosts.codeintel when the code-intel component is on |
a public URL, or empty (runtime) |
NEXT_PUBLIC_COMMERCIAL_URL (web; Compose: WEB_COMMERCIAL_URL) |
empty | absolute URL of a hosted/commercial offering; when set, the open-source pages show a "Hosted version" link to it, otherwise they show none | a URL (runtime) |
The Compose profiles code-intel, sandbox and events start the services
and the CLI sets these flags for you.
Agent and platform feature flags¶
Flags added by the "six for everyone" feature set and the agent-trust,
reliability and evaluation work. Each is a Spring property (app.*, shown as
its env name). Defaults are in application.yml; the comment above each key
says what it does and what it costs. The nine flags marked compose are
passed through by deploy/compose/docker-compose.yml (set them in
deploy/compose/.env); for the rest set the env var on the core container.
Ship dark (default false):
| Env | Enables | Needs |
|---|---|---|
APP_TUTOR_ENABLED compose |
Learn Anything tutor: study plans, graded quizzes, spaced-repetition review (/api/tutor, /learn/tutor). Limits: APP_TUTOR_MAX_PLANS_PER_USER, APP_TUTOR_MAX_LESSONS_PER_PLAN, APP_TUTOR_MAX_CURRICULUM_BYTES, APP_TUTOR_GENERATE_MAX_PER_USER_PER_HOUR |
|
APP_TUTOR_REMINDERS_ENABLED compose |
hourly in-app "reviews due" notification (no model call); APP_TUTOR_REMINDERS_MIN_GAP_HOURS |
tutor on |
RESEARCH_DEEP_ENABLED compose |
deep_research and verify_citations tools (bounded search + fetch + quote verification); RESEARCH_DEEP_* tune searches, fetches, sources, workers and deadline |
a web-search API key (otherwise an honest "unavailable" state) |
RESEARCH_RUNS_ENABLED compose |
background research runs (/api/research/runs, /research); RESEARCH_RUNS_MAX_PER_USER_PER_DAY, RESEARCH_RUNS_TIMEOUT_SECONDS |
RESEARCH_DEEP_ENABLED |
APP_SANDBOX_DATA_ANALYSIS_ENABLED compose |
upload CSV/XLSX/JSON files to the run_code sandbox; APP_SANDBOX_DATA_ANALYSIS_MAX_* and _TTL_MINUTES bound files, bytes, sessions |
an E2B account and code-execution template |
APP_SANDBOX_PLATFORM_E2B_ENABLED compose |
let tenants without their own E2B key use a platform-owned one, with daily run caps (APP_SANDBOX_PLATFORM_E2B_FREE_DAILY_RUNS, _PAID_DAILY_RUNS, _GLOBAL_DAILY_RUNS, _MAX_TIMEOUT_SECONDS) |
platform E2B credential |
E2B_RESTRICT_PUBLIC_TRAFFIC |
true (default): every E2B sandbox is created with public traffic to its URLs restricted and core sends the e2b-traffic-access-token header on every call; a create that returns no token is killed and fails closed. Set false only to roll back to open sandbox URLs |
an E2B account |
WHATSAPP_TEMPLATES_ENABLED compose |
send and list approved Meta WhatsApp templates (workflow Output node) | Meta WhatsApp Cloud API connection with a WABA id |
WORKFLOW_DRAFT_SIMULATE_SENDS compose |
draft workflow runs log "DRY RUN - would ..." for emails, messages and non-GET HTTP calls instead of sending | |
GRAPH_MEMORY_USER_FORGET_SYNC_ENABLED compose |
deleting a remembered fact also invalidates its graph edges | graph-memory profile; never exercised against real AGE |
OPENAI_COMPAT_ENABLED |
inbound OpenAI-compatible API (POST /v1/chat/completions, GET /v1/models) driving a platform agent; OPENAI_COMPAT_RATE_LIMIT_*, _MAX_PROMPT_CHARS, _TIMEOUT_SECONDS, _FIRST_TOKEN_WAIT_SECONDS |
JWT or vmcp_ grant key |
APP_A2A_CLIENT_ENABLED |
outbound A2A client (call remote agents); APP_A2A_CLIENT_ALLOWED_HOSTS is the egress allow-list, APP_A2A_CLIENT_REQUIRE_SIGNED_CARD stays true; APP_A2A_CLIENT_MAX_CALLS_PER_TURN (default 5, 0 = no cap) bounds remote calls per agent turn; APP_A2A_TASK_STORE=redis for more than one replica |
allow-listed hosts |
APP_MCPSERVER_ASYNC_TASKS_ENABLED |
MCP async tasks on the platform MCP server | |
MCP_OAUTH_ENABLED |
generic MCP OAuth (RFC 9728/8414/7591 + PKCE), API only; see docs/architecture/ADR-mcp-generic-oauth.md. MCP_OAUTH_STORE (memory/redis, default memory) chooses where the single-use state set, the token cache and the refresh single-flight live — redis is required when the API runs more than one replica (replica-strict boot refuses otherwise; the Helm chart sets it via core.replicaReadiness.mcpOAuthStoreRedis) |
MCP_OAUTH_REDIRECT_URI |
APP_MCP_REGISTRY_ROUTE_TOOL_CALLS |
route stdio MCP tool calls to the owning replica over Redis | Redis with auth/TLS |
CHAT_ESCALATION_ENABLED |
verifier-escalated model routing; CHAT_ESCALATION_KILL_SWITCH wins |
|
SWARM_VERIFIER_ENABLED |
goal verifier for delegate_parallel swarms |
|
CODING_HARNESS_ENABLED |
limits for the coding-agent harness (stage 0, nothing reads them yet) | |
CODING_HARNESS_LOCAL_WORKSPACE_DIR |
owner-only parent directory for the dev-only local runner's workspaces (default ~/.vegaduta/coding-sandbox; never the shared system temp dir) |
CODING_HARNESS_ALLOW_LOCAL_RUNNER |
MCP_PACKAGE_CACHE_DIR |
owner-only base directory for the per-registration npm/uv caches of stdio MCP servers spawned inside core (npm-cache/, uv-cache/, uv-tools/, uv-bin/ sub-roots, swept hourly for orphans; default ~/.vegaduta/mcp-package-cache, never the shared system temp root). When the default cannot be created (read-only container home) core warns and uses an owner-verified vegaduta-mcp-package-cache-<user> directory under the JVM temp dir; an explicit value that is unusable stops boot |
|
KNOWLEDGE_SPAN_CITATIONS_ENABLED |
span-level offsets and quote on search_knowledge_base hits |
|
EVAL_JUDGE_CALIBRATION_ENABLED |
judge calibration against human-labelled examples | |
LLM_STRUCTURED_OUTPUT_NATIVE_ENABLED |
provider-enforced JSON schemas (OpenAI json_schema, Gemini responseSchema) |
|
TOOL_SANDBOX_E2B_EXECUTION |
run custom Python/JS tools in an E2B microVM instead of tool-sandbox |
TOOL_SANDBOX_ENABLED, E2B credential |
APP_X402_FACILITATOR_URL (empty) |
x402 settlement; empty keeps payments in verification-only mode | |
APP_DELEGATION_ISSUER (empty) |
issuer claim for delegation tokens (defaults to the A2A public base URL) |
Default true (switch off with false):
| Env | Effect |
|---|---|
APP_MEMORY_BLOCKS_ENABLED, APP_MEMORY_BLOCKS_ACTIVE_PROJECT_ENABLED, APP_MEMORY_SELF_SERVICE_ENABLED |
editable memory blocks, project memory and the /memory page; size caps in APP_MEMORY_*_MAX_* |
APP_CHAT_DIRECTIVES_MEMORY_ENABLED, APP_CHAT_DIRECTIVES_MCP_UNTRUSTED_ENABLED, APP_CHAT_DIRECTIVES_TOOL_TRUST_ENABLED, APP_CHAT_SSE_MEMORY_EVENTS_ENABLED |
prompt directives and SSE events for memory and tool trust |
TOOL_CONFIRMATION_GATE |
tools classified as needing confirmation park for human approval; TOOL_CONFIRMATION_GATE_EXEMPT lists the exceptions |
APP_GUARDRAILS_FAIL_MODE |
closed (default) or open: what a guardrail does when its own machinery fails (tool-policy lookup throws, the injection judge errors or answers nothing, content moderation throws). closed refuses the call, withholds the output or refuses the turn; open lets it through with a warning. A violation always blocks. Not configured (no bean, no chat client) is not a failure. |
TOOL_UNCLASSIFIED_ACTION |
allow (default, counted), require-approval or deny: what a tool with no classification row gets. The last two stop a new or third-party tool acting until it is classified. |
TOOL_EFFECT_JOURNAL_ENABLED |
repeated write/send tool calls within TOOL_EFFECT_JOURNAL_TTL_MINUTES return the recorded result (TOOL_EFFECT_JOURNAL_STORE=redis to share across replicas) |
CHAT_TOOL_RESULT_CLEARING_ENABLED |
old tool results are cleared from the prompt past CHAT_TOOL_RESULT_CLEARING_THRESHOLD_CHARS, keeping the last CHAT_TOOL_RESULT_CLEARING_KEEP_LAST |
LLM_ANTHROPIC_THINKING_ENABLED |
extended-thinking mapping for Anthropic; thinking stays off until LLM_ANTHROPIC_THINKING_EFFORT (or a per-call option) sets an effort |
EVALS_FREE_PLAN_ENABLED, AGENT_LAB_FREE_PLAN_ENABLED |
eval datasets and Agent Lab knockout/consistency are available to tenants on the FREE plan with tighter caps (false = restore the premium-only 403) |
EVAL_CODE_GRADERS_ENABLED, EVAL_RUN_SNAPSHOT_ENABLED, EVAL_GATE_PASS_HAT_K_ENABLED, SIMULATION_ATTACK_SCENARIOS_ENABLED |
deterministic code graders, run snapshots, the pass^k promotion gate, attack-scenario simulations |
Other new settings: AGENT_RUN_BUDGET_USD (default 2.0, cost cap for delegation
and deep research per run, with AGENT_RUN_BUDGET_PER_AGENT_OVERRIDE_ENABLED and
AGENT_RUN_BUDGET_MAX_PER_AGENT_USD), APP_AGENTS_DEFAULT_GUARDRAILS
(default jailbreak, seeded on new agents; empty restores the old behaviour),
EVAL_DEFAULT_REPEATS (3, repeats per eval case for pass^k).
Web (runtime, see web/docs/RUNTIME-CONFIG.md): NEXT_PUBLIC_MCP_APPS=1 renders
sandboxed MCP Apps (ui:// resources) in chat; NEXT_PUBLIC_ARTIFACTS=1 turns
on the chat artifacts side pane (Compose: WEB_ARTIFACTS); NEXT_PUBLIC_TRUSTED_IMAGE_HOSTS
lists extra hosts model-rendered markdown images may load from (an entry
starting with . also matches subdomains); the API and site origins are always
trusted.
Flags added in the October 2026 sync¶
Same conventions as above (Spring property shown as its env name, defaults in
application.yml).
Ship dark (default false):
| Env | Enables |
|---|---|
TOOL_SEARCH_ENABLED |
on-demand tool loading through search_tools / call_tool |
TOOL_MCP_ANNOTATIONS_ENABLED |
classify MCP tools from their safety annotations |
TOOL_HTTP_REQUEST_SAFE_MODE |
http_request safe mode: https only, allow-lists, named credentials, gated writes |
APP_CHAT_RESUMABLE_STREAMS_ENABLED (Helm: core.resumableStreamsEnabled) |
chat streams that resume after a drop, sleep, refresh or navigation. APP_CHAT_RESUMABLE_STREAMS_STORE=redis (set by the chart for 2+ replicas) has no effect while this is off |
APP_CHAT_BRANCHING_ENABLED (Helm: core.branchingEnabled) |
keep chat history: each message is stored in Postgres for app.chat.branching.retention-days (30) instead of expiring from Redis after 24 h; edit, regenerate and switch between conversation branches. Also powers /api/chat/threads (the signed-in user's session list, which the Assistant merges into its thread rail) and the stored-tree fallback of the transcript reload after the 24 h Redis window; those routes answer 404 while off. On by default in lite.yml, docker-compose.yml, the chart and the personal Spring profile (application-personal.yml, since 0.1.9; APP_CHAT_BRANCHING_ENABLED=false in deploy/compose/core.env turns it off there); the core property itself defaults to off |
APP_CHAT_DIRECTIVES_ARTIFACTS_ENABLED |
prompt directive for the chat artifacts pane |
CHAT_IMPORT_ENABLED, CHAT_EXPORT_ENABLED |
import ChatGPT/Claude chats as read-only records; export chats as md/json/txt. Transcript reload, export and branching only ever reach the caller's own conversation: session memory is keyed by tenant + agent + user + client session id, and a session is readable only by the user who started it (see Security Architecture §4.1 in the platform docs) |
APP_MCP_APPS_ENABLED |
host MCP Apps (ui:// resources); the web side also needs NEXT_PUBLIC_MCP_APPS=1 |
APP_MCP_SERVER_EXPOSE_AGENTS_ENABLED |
expose agents and workflows as per-caller MCP tools |
MCP_OAUTH_PROVIDERS_ENABLED |
one-click provider OAuth (Google, GitHub, Slack, Notion, Linear, HubSpot, Microsoft, Asana, Zoom); per-provider MCP_OAUTH_<PROVIDER>_CLIENT_ID / _CLIENT_SECRET, MCP_OAUTH_PROVIDER_CALLBACK_BASE_URL and MCP_OAUTH_PROVIDER_CONSOLE_REDIRECT_URI (public URLs, never localhost) |
MCP_OAUTH_CIMD_ENABLED |
Client ID Metadata Documents for generic remote-MCP OAuth (MCP_OAUTH_CIMD_CLIENT_ID_URL) |
MCP_PIECE_HOST_ENABLED |
Activepieces piece-* catalog entries; needs the runner image from deploy/compose/docker-compose.piece-host.yml and MCP_STDIO_SIDECAR_ENABLED=true |
BROWSER_V2_ENABLED |
(default on in the personal profile, with profiles and takeover; Chromium comes from CORE_IMAGE_VARIANT=-full, see personal mode) browser agent v2 tools with domain allow/deny and vault secret placeholders; BROWSER_PROFILES_ENABLED (encrypted saved logins), BROWSER_RECIPES_ENABLED (record and replay), BROWSER_TOTP_ENABLED, BROWSER_TAKEOVER_ENABLED (human takeover) |
KNOWLEDGE_PARENT_CHILD_ENABLED, KNOWLEDGE_FUSION_ENABLED, KNOWLEDGE_QUERY_REWRITE_ENABLED |
parent-child chunking, tunable RRF/MMR fusion, query rewriting |
DATA_TABLES_ENABLED |
Data Tables service, REST API, agent tools and the data_table workflow node |
SCHEDULED_TASKS_ENABLED |
recurring agent prompts (/scheduled, schedule_task tool) |
WORKFLOW_FORM_TRIGGER_ENABLED, WORKFLOW_WAIT_NODE_ENABLED, WORKFLOW_NODE_ERROR_STRATEGY_ENABLED, WORKFLOW_PORTABILITY_ENABLED |
hosted form trigger, Wait node, per-node error strategies, workflow import/export |
QUALITY_TRAJECTORY_EVAL_ENABLED |
trajectory evals scored with pass^k |
AUDIT_SEAL_ENABLED |
signed, hash-chained audit seals; verify exports offline with scripts/verify-audit-export.py |
WHATSAPP_VOICE_NOTES_ENABLED |
WhatsApp voice notes in the sender's language (Meta Cloud API) |
LLM_STRUCTURED_OUTPUT_ANTHROPIC_TOOL_FORCING_ENABLED, LLM_STRUCTURED_OUTPUT_OLLAMA_FORMAT_ENABLED, LLM_TOOLS_STRICT_ENABLED |
provider-side structured output and strict tool schemas |
AGENT_JOB_OWNERSHIP_ENABLED |
agent-job ownership leases, cross-replica adoption and bounded resume |
RESILIENCE_CB_ENABLED, RESILIENCE_BULKHEAD_ENABLED |
circuit breakers (sidecar embeddings, E2B, search, Meta Graph) and bulkheads |
APP_OBSERVABILITY_TAIL_SAMPLING_ENABLED |
in-process tail sampling of errors and slow agent runs |
APP_STARTUP_BACKGROUND_WARMUP_ENABLED, APP_STARTUP_PROFILING_ENABLED |
move best-effort warm-ups off the main thread; start-up phase profiling |
TENANCY_MIGRATE_SKIP_UP_TO_DATE |
skip already-migrated tenant schemas on boot |
Default true: AGENT_IMPORT_ENABLED (import agents from OpenAI
Assistants, the Agents SDK and Flowise), APP_MEMORY_IMPORT_ENABLED (import
memories from another assistant), SKILLS_SUPPLY_CHAIN_ENABLED (scan, pin and
review skills), WORKFLOW_AI_NODES_ENABLED (question classifier, parameter
extractor, variable aggregator, document extractor), TOOL_HTTP_REQUEST_ENABLED
and TOOL_HTTP_REQUEST_WRITE_TOOL_ENABLED, PLATFORM_RELIABILITY_ENABLED and
PLATFORM_NORTH_STAR_ENABLED (admin Reliability and North Star pages), and the
per-provider MCP_OAUTH_<PROVIDER>_ENABLED switches (inert while
MCP_OAUTH_PROVIDERS_ENABLED is off).
Agent-runtime defaults set on 2026-10-23. Owner decision from the
agent runtime defaults review. The
base profile (application.yml, inherited by lite, full and team deployments) and the
personal profile (application-personal.yml) differ on purpose. Every one is still an
ordinary environment override; nothing else in the review changed (the turn journal,
moderation, pinning, trifecta, unclassified-tool action, escalation, the swarm verifier,
job resume, rerun and ownership, reasoning depth and injection screening keep their values).
| Env | Property | Base | personal |
What it does and what it costs |
|---|---|---|---|---|
AGENT_HOOKS_ENABLED |
app.agent-hooks.enabled |
true |
false |
declarative agent lifecycle hooks in the tool guard chain and the chat turn. Inert until someone defines a hook (one cached 15 s lookup per tool call). Set app.agent-hooks.webhook.allowed-hosts and signing-secret before defining a webhook hook. |
TOOL_ARG_POLICIES_ENABLED |
app.tool-arg-policies.enabled |
true |
false |
per-agent tool argument policies, evaluated before a tool runs. Inert until a policy exists; a policy lookup that throws follows app.guardrails.fail-mode. |
APP_AGENTS_TAINT_MODE |
app.agents.taint.mode |
observe |
enforce (as before) |
after a turn read content an outsider could write, count and audit the consequential calls that WOULD need approval. Behaviour unchanged; one audit row and one WARN per tool per tainted turn. enforce is the next step once the audit rows have been reviewed; off switches the layer off. |
SKILLS_SUPPLY_CHAIN_ENFORCE |
app.skills.supply-chain.enforce |
true |
false |
a custom skill whose content changed since an admin approved it, or that scans HIGH and was never approved, is withheld from load_skill and the skill list until an admin re-approves it on the Skills page or in the platform-admin Skill security page. Built-in skills are never withheld. The scan and the pins (SKILLS_SUPPLY_CHAIN_ENABLED) stay on in personal. |
APP_CONTEXT_COMPACTION_ENABLED |
app.context.compaction.enabled |
true |
true |
context budget planner and compaction in the chat turn (restore_tool_result stays on with it). A prompt inside the model's window is untouched; above 60% old tool results become restorable placeholders, above 85% the oldest turns become one summary written by the agent's own model unless app.auxiliary-model.compaction names another (a failed summary call is skipped). Costs: a compaction changes the prompt prefix, so a provider prompt cache misses once. |
AGENT_JOB_STEP_JOURNAL_ENABLED |
app.agent-jobs.durability.step-journal-enabled |
true |
true |
durable step journal of agent-job turns (observability only; fail-soft; rows purged after AGENT_JOB_STEP_JOURNAL_RETENTION_DAYS, 14). AGENT_JOB_RESUME_FROM_JOURNAL, AGENT_JOB_RESUME_RERUN_INCOMPLETE_TURN and AGENT_JOB_OWNERSHIP_ENABLED stay false. |
Other new settings:
APP_REPLICA_MODE(single): setmultiwhen more than one core replica shares a database; the startup check then lists every in-memory store that would split state across replicas (a warning each). WithAPP_REPLICA_MODE_STRICT=truethe fatal ones refuse to boot. The Helm chart sets both for you when the API runs more than one replica (Deployment shapes).app.chat.hotpath.agent-projection-ttl(3s, capped at30s): how long a pod keeps its snapshot of an agent's read-only settings between turns. A write on the same pod evicts it at once; another replica sees the change after at most this long.0sreads the row every time. Authorization never uses it. See Hot-path performance.app.chat.hotpath.plan-cache-ttl(0s, capped at30s): how long a pod keeps the tenant's resolved plan between turns. Off by default: a turn now resolves the plan once and passes it down, so the cache could save at most one lookup per turn (the measured reasoning is in Hot-path performance). A plan change on the same pod evicts at once; another replica sees it after at most this long. Never used for the tenant kill switch.app.chat.hotpath.config-lookup-ttl(3s, capped at30s): how long a pod keeps "is this integration configured" answers (any MCP server, the ElevenLabs, Sarvam and E2B credentials, image and video providers, payment mandates, specialist tags, platform tool credentials). A write on the same pod evicts at once; another replica sees it after at most this long.0sturns the cache off.app.chat.session-append-single-roundtrip(true): append one conversation turn to Redis in one Lua call instead of 3 to 6 sequential commands. Same ordering, TTL and trimming;falserestores the multi-call sequence, which is also the automatic fallback if Redis refuses the script before running it (ACL, scripting disabled, Redis Cluster cross-slot). A timeout or dropped connection is not replayed, since the script may have run.app.chat.cancel-poll-interval(500ms): how often a pod checks Redis for cancel flags of its in-flight chat turns. One Lua call on the shared connection covers every turn on the pod, and nothing is sent while none is streaming. Worst case from a cancel request to the turn stopping is this interval plus one Redis command.app.chat.stream-end.async(false): run the best-effort work at the end of a chat stream (output-guardrail audit,agent.message.completedevent, RL episode close, LLM call-log row) on a small pool instead of the thread that delivered the last token. Off until measured.app.chat.stream-end.threads(4),app.chat.stream-end.queue(1000; a full queue runs the work on the streaming thread, nothing is dropped),app.chat.stream-end.drain-timeout(10s; queued work is run on a graceful stop). A hard kill loses what is still queued. See Hot-path performance.app.turn-journal.enabled(false, envAPP_TURN_JOURNAL_ENABLED): write a hash-chained journal of each chat turn's stages (intake, model call, tool calls, approvals, outbound guard, verify, commit) to the tenant tableturn_journal_event. It covers every reasoning depth (streamed, CAREFUL, RIGOROUS, PLANNED) and the turns a delegated agent or a specialist runs for another agent's tool call (each its own record; its INTAKE names the parent turn). A tool call is journaled asok,error,blocked(a guard, policy, hook, breaker or budget answered instead of the tool) orparked(held for a human; it also writes anAPPROVALevent with the approval request id, and the turn that continues the task after the decision writes the matchingdecided:...event). Hashes and short redacted summaries only, never prompts or tool payloads. Off, behaviour is unchanged. While on, the chat response carries the turn's id inX-Journal-Turn-Id. Read one turn withGET /api/agents/{agentId}/turns/{turnId}/journal, or list a session's turns withGET /api/agents/{agentId}/turn-journal?sessionId=...&limit=...(both need the audit-log permission and VIEWER on the agent). Only a digest of the session id is stored (tenant migration V666), so turns written before that migration cannot be found by session. See ADR-0020.app.turn-journal.max-list(100, envAPP_TURN_JOURNAL_MAX_LIST): the most turns one by-session listing returns; a request'slimitis clamped to it.app.turn-journal.retention-days(90, envAPP_TURN_JOURNAL_RETENTION_DAYS): a sweep deletes whole turns (every event of a turn, so a kept turn's hash chain is never missing its head) whose newest event is older than this, in every tenant schema.0keeps the journal for ever (no sweep). Inert whileapp.turn-journal.enabledis off. Before this setting nothing purgedturn_journal_event; a deployment that turned the journal on and wants the old keep-everything behaviour sets0.app.turn-journal.purge-interval(6h, envAPP_TURN_JOURNAL_PURGE_INTERVAL) is how often it runs;app.turn-journal.purge-batch-turns(500, envAPP_TURN_JOURNAL_PURGE_BATCH_TURNS) turns are deleted per statement and at mostapp.turn-journal.purge-max-batches(40, envAPP_TURN_JOURNAL_PURGE_MAX_BATCHES) statements run per tenant per sweep, so one sweep is bounded.app.wiring.strict-gateways(false, envAPP_WIRING_STRICT_GATEWAYS): at readiness the application checks that the static holders a switched-on feature needs (the turn journal and the four learning loops) were installed by the feature's bean. A holder that was not installed is logged as an ERROR naming the feature and the flag; with this on, the start fails instead. Turn it on in an environment where a feature that is switched on but silently inert would be worse than a failed deploy.- A2A signing keys:
APP_A2A_SIGNING_ENABLED(true) signs Agent Cards (Ed25519) and, with the same key, delegation tokens and audit seals (AUDIT_SEAL_ENABLED). WithAPP_A2A_SIGNING_PRIVATE_KEY,APP_A2A_SIGNING_PUBLIC_KEY(PEM) andAPP_A2A_SIGNING_KEY_IDempty (the default) each boot, and each replica, signs with an ephemeral key, so cards verify at/.well-known/jwks.jsononly until restart. Pin all three for a stable identity; the Helm chart refuses more than one API replica untilcore.replicaReadiness.a2aSigningKeysPinnedistrue(you supply the keys through the app Secret). APP_DELEGATION_MAX_TTL_SECONDS(3600): ceiling on any delegation token's life and on a grant's TTL.app.scheduling.skip-bean-classes,app.scheduling.keep-bean-classes,app.scheduling.skip-listener-classes: which scheduled beans and boot listeners a shape or pool leaves to another. A class name or a package prefix ending in*;keep-bean-classescarves exceptions out of a broad skip. Set by theshape-*profiles, not by hand.app.scheduling.pool(APP_SCHEDULING_POOL, empty),app.scheduling.offloaded-pools(APP_SCHEDULING_OFFLOADED_POOLS, empty): named pools of the jobs shape, defined inapp.scheduling.pools.<name>(application-shape-jobs.yml:ingest,webhooks,intake,billing). On a pool pod,pool=<name>skips every product scheduled bean except that pool's (with theshape-jobs-poolprofile, which also skips the boot sweeps another shape owns). On the main jobs pod,offloaded-pools=<a,b>skips the classes of pools that run elsewhere. Both empty = the pod runs everything its shape owns. An unknown pool name fails the boot. The two are exclusive. Set by the Helm pool charts, not by hand.TOOL_TRIFECTA_ENFORCEMENT(warn): lethal-trifecta enforcement for agents combining private data, untrusted content and external communication -off,warn,require-approvalorblock.- Web search:
WEB_SEARCH_PROVIDER(brave),WEB_SEARCH_FALLBACK(empty), per-provider daily capsWEB_SEARCH_CAP_<PROVIDER>(0= unlimited), and for SearXNGWEB_SEARCH_SEARXNG_BASE_URL,_CATEGORIES,_ENGINES,_LANGUAGE. Tavily, Exa and Serper keys are entered in the admin console. A self-hosted SearXNG is indeploy/compose/docker-compose.search.yml(profilesearch). - Billing:
DEFAULT_PAYMENT_GATEWAY(razorpay); Stripe viaSTRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY,STRIPE_WEBHOOK_SECRET,STRIPE_PRICE_ID_PREMIUM,STRIPE_PRICE_ID_UNLIMITED(blank = Stripe reports not configured; an empty webhook secret fails closed).BILLING_EMAIL_UPGRADE_URL(billing.email.upgrade-url, empty): link used by the complimentary-plan reminder and "now on Free" e-mails; blank =<app.web.public-url>/billing.
Outbound timeouts and tool search¶
Verified against application.yml and application-workflow.yml. Spring properties; the env name is the override.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.llm.http.connect-timeout |
LLM_HTTP_CONNECT_TIMEOUT |
10s |
connect timeout to an LLM provider; the one that fires in practice (outage, DNS blackhole, a wrong Ollama base URL) |
app.llm.http.read-timeout |
LLM_HTTP_READ_TIMEOUT |
10m |
time until the provider starts responding, not a limit on the whole answer: a hang detector |
app.llm.http.idle-connection-timeout |
LLM_HTTP_IDLE_CONNECTION_TIMEOUT |
120s |
how long an idle pooled provider connection may live; keep it below a NAT idle window (about 4 minutes on Azure). 0s keeps the JDK default |
app.llm.http.stream-timeouts-enabled |
LLM_HTTP_STREAM_TIMEOUTS_ENABLED |
true |
escape hatch: false hands back the auto-detected streaming connector and gives up streaming-path timeouts |
app.llm.http.stream-idle-timeout |
LLM_HTTP_STREAM_IDLE_TIMEOUT |
90s |
the model may be silent this long before the fast-depth stream ends with a timeout. It counts model silence only: while a tool runs (a browser task, a coding task, an agent-to-agent call) the timer is suspended, up to app.chat.resilience.tool-idle-ceiling, and it starts again when the tool finishes. The personal profile sets 240s because a local model can spend that long evaluating a prompt before its first token |
app.llm.http.buffered-turn-timeout |
LLM_HTTP_BUFFERED_TURN_TIMEOUT |
240s |
total ceiling for one buffered-depth turn (careful, rigorous, planned). A turn that carries an enclosing deadline longer than this (an agent job's own timeoutSeconds) runs until that deadline instead, so a job given 30 minutes is not cut at four. When it is reached after tools ran, the turn is reported with the steps that ran (see app.chat.resilience); it is no longer answered again at fast depth without its tools |
app.llm.retry.max-attempts |
LLM_RETRY_MAX_ATTEMPTS |
2 |
Group app.llm.retry. Attempts per model call, the first one included, for a transient failure: HTTP 5xx, HTTP 429, a connection that failed before a response. Applies to every non-streaming call (per HTTP round trip: a tool loop is never replayed by it) and, with stream-pre-first-token, to a streaming turn that fails before its first token and before any tool ran. A read timeout, a refused model-server URL and any other 4xx are never retried. 1 never retries |
app.llm.retry.initial-backoff |
LLM_RETRY_INITIAL_BACKOFF |
1s |
Wait before the first retry; each further retry doubles it |
app.llm.retry.max-backoff |
LLM_RETRY_MAX_BACKOFF |
10s |
The longest the exponential backoff grows to |
app.llm.retry.jitter |
LLM_RETRY_JITTER |
0.5 |
Fraction (0 to 1) of each backoff that is randomised away so callers that failed together do not retry together: wait = exponential x (1 - jitter x random). 0 is deterministic |
app.llm.retry.retry-rate-limited |
LLM_RETRY_RATE_LIMITED |
true |
Retry HTTP 429. false sends a rate limit straight to the caller's provider fallback, as before this setting existed |
app.llm.retry.honor-retry-after |
LLM_RETRY_HONOR_RETRY_AFTER |
true |
Wait at least what the provider asked for. Read from the Retry-After / retry-after-ms header where the client still has it (the streaming client), otherwise from the wait the provider states in its error body ("try again in 3.5s", Gemini's retryDelay): Spring AI's default error handler drops the headers of a blocking call |
app.llm.retry.max-retry-after |
LLM_RETRY_MAX_RETRY_AFTER |
30s |
A provider asking to wait longer than this is not retried here, so the fallback provider (BYOK fallback, hosted pool) takes over instead of the turn sitting idle |
app.llm.retry.stream-pre-first-token |
LLM_RETRY_STREAM_PRE_FIRST_TOKEN |
true |
Retry a streaming turn that fails before its first token, under the same attempts and backoff. Never once a token was sent or a tool ran (the retry would replay both) |
app.http.hcaptcha.connect-timeout / read-timeout |
HCAPTCHA_CONNECT_TIMEOUT / HCAPTCHA_READ_TIMEOUT |
3s / 5s |
the hCaptcha verification call |
spring.mail.properties.mail.smtp.connectiontimeout / timeout / writetimeout |
SMTP_CONNECT_TIMEOUT_MS / SMTP_READ_TIMEOUT_MS / SMTP_WRITE_TIMEOUT_MS |
15000 / 60000 / 60000 |
milliseconds; the workflow Output node's email relay (workflow profile). JavaMail's own default is no timeout |
app.tools.search.enabled / threshold |
TOOL_SEARCH_ENABLED / TOOL_SEARCH_THRESHOLD |
false / 25 |
on-demand tool loading (flag); deferral applies only to an agent with more tools than the threshold |
app.tools.search.always-on |
TOOL_SEARCH_ALWAYS_ON |
memory, knowledge, skills, delegation and restore_tool_result tools |
comma-separated exact tool names or prefix* patterns that always stay in the tool list |
app.tools.search.max-carry-over |
TOOL_SEARCH_MAX_CARRY_OVER |
6 |
already-searched tools still offered directly on later turns of the same session; 0 = none |
app.tools.run-subtasks.enabled |
TOOL_RUN_SUBTASKS_ENABLED |
true |
offers run_subtasks to every agent that may delegate: up to 8 independent briefs run in parallel as throwaway copies of the agent (no delegation, scheduling, human-request or terminal-write tools inside a branch). false removes the tool |
app.tools.create-workflow.enabled |
TOOL_CREATE_WORKFLOW_ENABLED |
true |
offers create_workflow to an agent that lists it in its tool allow-list (the Assistant does): drafts a workflow graph from a plain-language instruction and saves it as a DRAFT. It is never activated or run. false removes the tool |
Timeouts, failover and retries when tools are involved¶
The tool loop runs inside the model call, so from the outside a turn that is working looks like a turn that is stuck: no token arrives while a tool runs. Four rules follow from that, each with the settings above.
- The idle timeout counts the model, not the tools.
app.llm.http.stream-idle-timeout(90 s) is the longest the model may be silent. A running tool suspends it, up toapp.chat.resilience.tool-idle-ceilingsince the tool last started, finished or reported progress, and it starts again when the tool returns. Only tools the platform wraps for logging report their activity (every tool the agent resolves does; a raw tool a caller passes in as an extra tool does not). - A buffered turn that runs out of time says what ran.
app.llm.http.buffered-turn-timeout(240 s) ends a careful, rigorous or planned turn. If tools had run, the person gets the steps that completed and the ones still running, the report is kept in the transcript (so a follow-up does not run them again) and is never cached. If no tool had run, the turn is answered again at fast depth with its tools on offer. A turn carrying an enclosing deadline, an agent job'stimeoutSeconds, runs until that deadline when it is longer than 240 s. - A turn that ran a tool is not replayed on another provider unless that is safe. Failing over to the BYOK fallback provider or to the next rung of the hosted pool runs the whole turn again. It is refused when a tool is still running, when a tool with side effects ran without an effect-journal record, or when an unclassified tool with a mutating name ran; read-only tools, and write/send tools the journal recorded, do not block it (
app.chat.resilience.replay-after-journaled-effects). A refusal is reported as the provider's own failure, counted inchat.failover.blocked, and noted on the step-event channel. - Transient failures are retried before another provider is tried.
app.llm.retry.*: HTTP 429 and 5xx, with exponential backoff, jitter and the provider's Retry-After.POST /api/completions/agent-turngoes to the next pool row only for a failure that is the row's own (key, billing, model, capacity, 5xx); an input-shaped failure (413/422, context length, content policy) is answered at once.
What the effect journal covers. The journal (app.tools.effect-journal.*, on by default, in memory per node unless store=redis) records the result of a successful call of a tool the tool classification marks DURABLE: the built-in write/send tools (for example google_gmail, google_calendar and its move/cancel tools, google_drive, google_sheets, upload_video_to_youtube, data_table_write, http_request_write, a2a_call_agent, add_to_knowledge_base, remember_fact, local_shell, generate_document), any tool a tenant classified DURABLE, and, when app.tools.mcp-annotations.enabled is on, an MCP tool whose annotations do not say it is idempotent. (For the failover decision an MCP annotation alone is not trusted: such a tool counts as unclassified and is judged by its name.) A repeat of the same tool with the same arguments in the same turn (key: tenant, session, turn, tool, arguments hash), within ttl-minutes, returns the recorded result and does not reach the provider; in an agent job session with the durable step journal on, the key ignores the turn so the repeat is recognised after a restart. It does not cover: a call that threw (a failure after the provider acted looks the same as one before it, so it is not recorded and a replay is blocked by the gate above); the same tool with different arguments; tools classified SIDE_EFFECT or not classified at all (those are judged by their classification's replay verdict, then by a name rule that treats unknown names as mutating); a repeat in a later turn of an ordinary chat session; and calls that do not pass through the platform's tool wrapper.
Tool calls: budget, rounds and result size¶
Three different limits apply to a turn that uses tools. They count different things and are read in this order. The settings below are three groups: app.guardrails.step-limit (calls), app.chat.tools (rounds, the wording of the stops, result clearing) and app.tools.output (result size).
Calls. A call is one tool invocation. The budget a turn is given is in calls, counted once across every model pass of the turn (parallel calls each count). app.guardrails.max-tool-calls-per-turn is the ceiling for an agent on the default autonomy tier; the SUPERVISED and FULLY_AUTONOMOUS tiers scale it by the app.guardrails.step-limit.* settings below. The first calls over the ceiling are not run: the model is handed the structured result {"error":"tool_budget_exhausted","result":"..."} (the same one the tool loop below uses) and can write its final answer. A model that ignores it and keeps calling is stopped with an error (HTTP 429 on a non-streaming endpoint).
Rounds. A round is one model-then-tools iteration inside a single model invocation of Spring AI's tool loop. app.chat.tools.max-rounds-per-invocation is only a backstop for loops that make no countable calls (a model inventing tool names). Every executed round makes at least one call, so the default 0 derives the cap from the call ceiling instead of giving it a number of its own: the highest per-turn ceiling (max-tool-calls-per-turn times autonomous-multiplier) plus grace-calls plus one, so the call policy always decides first. Before, a hidden constant of 25 rounds ended turns early: a turn configured for 30 calls (a FULLY_AUTONOMOUS agent) or 150 (the personal profile) never got past round 25. An explicit positive number wins, and the log warns at startup if it is below the call ceiling. A per-agent or per-tenant call ceiling above the platform's highest one raises the cap for that agent's turns (see Agent runtime limits below); an explicit positive number still wins.
Result size. app.tools.output.max-chars is the most characters one tool result may hand to the model, for built-in and MCP tools alike. A longer result is truncated: the head is kept, followed by a marker that states how much was cut and how to ask for a narrower slice; the total, marker included, never exceeds the cap. It used to be discarded outright ("failed validation"), and plain text that merely started with [ or { (a log line, a numbered list) was withheld as "malformed JSON"; neither happens now. app.tools.output.per-tool.<tool name> gives one tool its own cap.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.guardrails.max-tool-calls-per-turn |
GUARDRAILS_MAX_TOOL_CALLS_PER_TURN |
15 (150 in the personal profile) |
the call ceiling for one turn on the default autonomy tier |
app.guardrails.step-limit.grace-calls |
APP_GUARDRAILS_STEP_LIMIT_GRACE_CALLS |
10 |
calls past the ceiling that are answered with the tool_budget_exhausted result before the turn is stopped with an error. Parallel calls of the round that crossed the ceiling count here. 0 stops the turn at the first call over |
app.guardrails.step-limit.supervised-divisor |
APP_GUARDRAILS_STEP_LIMIT_SUPERVISED_DIVISOR |
2 |
a SUPERVISED agent gets the ceiling divided by this |
app.guardrails.step-limit.supervised-min-steps |
APP_GUARDRAILS_STEP_LIMIT_SUPERVISED_MIN_STEPS |
3 |
and never fewer than this, so it can still finish a trivial multi-step task |
app.guardrails.step-limit.autonomous-multiplier |
APP_GUARDRAILS_STEP_LIMIT_AUTONOMOUS_MULTIPLIER |
2 |
a FULLY_AUTONOMOUS agent gets the ceiling times this |
app.guardrails.step-limit.exhausted-message |
(none) | text in application.yml |
model-facing. The result of the budget-exhausted tool result for a call over the ceiling; {limit} is the ceiling that applied A registry override of toolresult.tool_budget.calls (prompts) wins over this value; changing this value still works and is used when no override exists. |
app.chat.tools.max-rounds-per-invocation |
CHAT_TOOL_MAX_ROUNDS_PER_INVOCATION |
0 |
rounds one model invocation may run before the soft stop (every call answered with the budget-exhausted result, then a closing message if the model ignores it). 0 = derived from the call ceiling as described above |
app.chat.tools.max-unknown-calls-per-invocation |
CHAT_TOOL_MAX_UNKNOWN_CALLS_PER_INVOCATION |
8 |
calls to tool names the model was never offered, per model invocation, before the soft stop |
app.chat.tools.budget-exhausted-message |
(none) | text in application.yml |
model-facing. The result of the round-cap soft stop; {rounds} is the rounds used A registry override of toolresult.tool_budget.rounds (prompts) wins over this value; changing this value still works and is used when no override exists. |
app.chat.tools.hard-stop-reply |
(none) | text in application.yml |
the closing reply when the model keeps calling tools after the round-cap soft stop A registry override of message.tool_budget.hard_stop (prompts) wins over this value; changing this value still works and is used when no override exists. |
app.chat.tools.result-clearing.enabled / threshold-chars / keep-last |
CHAT_TOOL_RESULT_CLEARING_ENABLED / _THRESHOLD_CHARS / _KEEP_LAST |
true / 24000 / 3 |
once the tool results of one reply total more than the threshold, older results beyond the newest keep-last are replaced by a placeholder on the next loop iteration. The placeholder is a JSON object ({"context_cleared":true,"tool":...,"chars":...,"restore":...}): Spring AI's Gemini adapter parses every tool result as JSON and failed the turn on the earlier plain-text form |
app.chat.tools.result-clearing.restore-hint |
(none) | call the tool again if you need this result |
model-facing. The restore field of that placeholder |
app.tools.output.max-chars |
APP_TOOLS_OUTPUT_MAX_CHARS |
20000 |
characters one tool result may hand to the model, truncation marker included |
app.tools.output.per-tool.<tool name> |
(none) | empty | a tool's own cap in place of max-chars, for a tool that pages its output (more room) or a noisy one (less); zero or negative entries are ignored. Example: app.tools.output.per-tool.workspace_read: 60000 |
app.tools.output.truncation-marker |
(none) | text in application.yml |
model-facing. Appended to a cut result; {total} is the original length, {dropped} what was cut, {limit} the cap that applied |
Caps that look similar and are not this one:
| Where | Cap | Why it is separate |
|---|---|---|
workspace_read (file tool) |
pages to fit app.tools.output.max-chars (or its per-tool entry) |
the tool stops early enough that the whole JSON answer, including its "read on with offset=N" note, fits; it used to read up to 200,000 characters and have the middle of the JSON cut off by the guard |
MCP client (McpToolOutputSanitizer) |
64 KB, fixed | a security ceiling on what the platform will hold and scan from an untrusted server. The result is then cut to app.tools.output.* |
| OpenAPI tools | 16,000 characters, fixed | the tool shapes its own answer; below the cap |
| Knowledge search, data tables, tool search | 12,000 characters, fixed | the same |
| Step journal | 64,000 characters, fixed | what is stored for durable replay; the model never sees more than the cap above |
Approval record (AgentToolCallApprovalExecutor) |
32 KB, fixed | what is stored on the approval request; the continuation hands the model at most app.approvals.continuation.max-result-chars |
The edge relay (Edge relay) has its own table of settings (EDGE_RELAY_*, on by default).
Agent runtime limits: platform, tenant and agent¶
Seven limits of a turn can be set per tenant and per agent instead of only for the whole deployment: tool calls per turn, wall-clock time of a turn, wall-clock time of one tool call, characters of one tool result, knowledge-search top-k and min-score, and continuation passes. One resolver (AgentRuntimeSettingsResolver) decides them, and every path reads its answer: the streamed (FAST) and buffered (CAREFUL, RIGOROUS, PLANNED) turns, the hosted pool, agent jobs and the turn the platform starts after an approval. An agent job's own tool budget and timeout are enforced inside its turn, not only between turns. (Agent Lab runs a single tool-less completion, so none of the seven applies to it.)
The settings group is app.agent-runtime (with the sub-groups app.agent-runtime.defaults and app.agent-runtime.bounds).
Layers, lowest first: the platform (application*.yml), the tenant (/api/agent-runtime/limits, tenant admin), the agent (/api/agents/{id}/runtime-limits, an editor of the agent) and the request (a cap one run carries, such as an agent job's remaining tool budget; it can only tighten a value, never raise it). A layer that does not set a value leaves it to the layer below. GET on either endpoint returns the layer being edited, the values a turn runs under, which layer set each of them and the allowed range of each.
| Setting (API name) | Platform value comes from | Meaning |
|---|---|---|
maxToolSteps |
app.guardrails.max-tool-calls-per-turn, scaled by the autonomy tier |
tool calls one turn may make. A tenant value is a base the autonomy tier scales like the platform's; an agent's own value is the ceiling itself and is not scaled again. The tool loop's round backstop follows it (never below the platform's) |
turnTimeoutSeconds |
app.llm.http.buffered-turn-timeout (240 s) |
wall-clock limit of a turn. A buffered turn always has one. A FAST stream has none unless a tenant or agent sets one or an agent job gives it its deadline. A job deadline sooner than the limit ends the turn at the deadline; a later one extends the platform default but not an explicit tenant or agent limit |
toolTimeoutSeconds |
app.agent-runtime.defaults.tool-timeout-seconds (0 = none) |
wall-clock limit of one tool call. A call that outlives it is interrupted and the model is told (app.agent-runtime.tool-timeout-message); it counts as a tool failure for the circuit breaker. Runs the call on its own virtual thread (the tenant, version pin and MDC are carried over); a tool that ignores the interrupt keeps running in the background but no longer holds the turn |
toolOutputMaxChars |
app.tools.output.max-chars |
characters of one tool result that reach the model. Replaces the default every tool gets; a tool the operator gave its own app.tools.output.per-tool.* cap keeps it |
retrievalTopK |
app.agent-runtime.defaults.retrieval-top-k (3) |
chunks one search_knowledge_base call returns |
retrievalMinScore |
app.agent-runtime.defaults.retrieval-min-score (0 = every hit) |
least relevance a returned chunk needs, where relevance is the negated distance the hybrid search reports: the fused (reciprocal-rank) score, a few hundredths, or the reranker's score on its own scale |
continuationPasses |
app.chat.continuation.max-passes |
extra model passes after a reply that stopped at a step it announced and did not take (0 = never). Does not cover app.chat.finish.max-continuations, the passes that finish a reply cut off at the output limit |
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.agent-runtime.cache-ttl |
APP_AGENT_RUNTIME_CACHE_TTL |
10s |
how long a tenant's defaults are cached; a change reaches other replicas within it |
app.agent-runtime.tool-timeout-message |
(none) | text in application.yml |
model-facing. What the model is told when a tool call is stopped; {tool} and {seconds} are filled in |
app.agent-runtime.defaults.tool-timeout-seconds |
APP_AGENT_RUNTIME_DEFAULT_TOOL_TIMEOUT_SECONDS |
0 |
platform per-call tool limit; 0 = no limit, each tool keeps its own timeout |
app.agent-runtime.defaults.retrieval-top-k |
APP_AGENT_RUNTIME_DEFAULT_RETRIEVAL_TOP_K |
3 |
platform top-k of the knowledge-search tool |
app.agent-runtime.defaults.retrieval-min-score |
APP_AGENT_RUNTIME_DEFAULT_RETRIEVAL_MIN_SCORE |
0.0 |
platform min-score; 0 = no cut-off |
app.agent-runtime.bounds.<setting>.min / .max |
(none) | max-tool-steps 1-500, turn-timeout-seconds 10-3600, tool-timeout-seconds 1-1800, tool-output-max-chars 500-200000, retrieval-top-k 1-50, retrieval-min-score 0-1, continuation-passes 0-16 |
the least and most a tenant or an agent may set a value to (inclusive). <setting> is the kebab-case API name. A write outside the range is a 400 naming every offending setting and its range; a value stored earlier is clamped into the range when read, so tightening a bound tightens the overrides already stored. The platform layer is never clamped |
The persisted layers are nullable columns on agent and one row of tenant_runtime_limits (migration V671); null means "not overridden here", so no existing agent or tenant changes behaviour. The agent's overrides are also returned as runtimeLimits on the agent (GET /api/agents/{id}), omitted when the agent overrides nothing, and are not part of the agent's version snapshot (a hidden A/B treatment clone runs under its control agent's limits, like the run cap).
Conversation-aware turns: follow-ups, continuation and routing¶
A turn is not judged on its last message alone. Three behaviours read the conversation, and each has its own settings: the groups app.chat.follow-up, app.chat.continuation and app.routing.simple (plus the keys listed after them).
A short follow-up keeps the task's tools and its model. "yes", "continue" and "try again" share no word with any tool, so tool selection used to cut the agent to the four tools that match nothing (the tool set then changed in the middle of a task, which also changes the request prefix a provider's prompt cache is keyed on), and a cost-routing agent sent the message, which reads as trivial, to its economy model. A message of at most app.chat.follow-up.short-message-chars characters (Unicode code points, so it is language-neutral) is a follow-up: the tools the session already used in the last sticky-tools-window (at most max-sticky-tools, most recent first, read from the tool-call log) stay on offer, and if the session used tools the turn is not sent to the economy model (keep-primary-model). A message with real content is selected by its own words, as before. The first 200 logged calls in the window are read, so keep the window short on a session that makes hundreds of calls.
A reply that stops at a step it announced is continued the same way on every reasoning depth. The provider's tool loop ends when the model returns no tool call, so "Let me create the file:" followed by nothing looks like a finished turn. The continuation pass is sent the real conversation: the model's own tool calls and the tool results as tool messages (with their ids and provider metadata), and the original request with its attachments. It is never made for a turn parked on a human approval, a pass after the first must have called a tool, and app.chat.continuation.max-passes bounds the passes. Whether a reply reads as unfinished is a wording check on its last sentence, so it runs by default only for a weak tool caller (heuristic-scope: SMALL_MODELS): a platform-hosted row in small-model mode or measured at a heuristic-tool-grades grade, or a bring-your-own provider in heuristic-providers. A frontier model's text-only reply is an answer, and "Let me know if you need anything else" is a closing, not a step. The patterns and the nudge are per language tag (intent, closing, done, asks, unfinished-ending, nudge); a reply is checked against every language and the one that matched picks the nudge, so a language is added by adding its keys. ALL_MODELS applies the wording check to every model; heuristic-enabled: false turns it off.
Cost routing and tool selection work in any script. Words are matched in any script (text written without spaces is matched as pairs of characters), so tools described in the message's own language are found. Tool names and descriptions are in Latin script, so a message in another script that matches no tool says nothing about relevance: with app.tools.auto-selection.unscorable-uses-full-budget it gets the full per-turn tool budget rather than the small no-match one. The "simple message" heuristic behind cost routing reads app.routing.simple.* (length, number of sentences, where a sentence ends, signal words per language) and, with non-latin-simple: false, never calls a message in a script its signal words do not cover simple.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.chat.follow-up.sticky-tools-enabled |
APP_CHAT_FOLLOW_UP_STICKY_TOOLS_ENABLED |
true |
keep the tools the session already used on offer for a short follow-up |
app.chat.follow-up.short-message-chars |
APP_CHAT_FOLLOW_UP_SHORT_MESSAGE_CHARS |
40 |
a message of at most this many characters is a follow-up; 0 turns the follow-up rules off |
app.chat.follow-up.sticky-tools-window |
APP_CHAT_FOLLOW_UP_STICKY_TOOLS_WINDOW |
PT2H |
how far back in the tool-call log "tools the session already used" looks |
app.chat.follow-up.max-sticky-tools |
APP_CHAT_FOLLOW_UP_MAX_STICKY_TOOLS |
12 |
the most tools kept on offer this way, most recently used first |
app.chat.follow-up.keep-primary-model |
APP_CHAT_FOLLOW_UP_KEEP_PRIMARY_MODEL |
true |
a follow-up in a session that used tools is never routed to the economy model |
app.chat.continuation.heuristic-enabled |
APP_CHAT_CONTINUATION_HEURISTIC_ENABLED |
true |
false never re-prompts on the wording of a reply |
app.chat.continuation.heuristic-scope |
APP_CHAT_CONTINUATION_HEURISTIC_SCOPE |
SMALL_MODELS |
SMALL_MODELS = only a weak tool caller; ALL_MODELS = every model |
app.chat.continuation.heuristic-providers |
APP_CHAT_CONTINUATION_HEURISTIC_PROVIDERS |
OLLAMA,LOCAL_OPENAI |
bring-your-own providers treated as weak tool callers |
app.chat.continuation.heuristic-tool-grades |
APP_CHAT_CONTINUATION_HEURISTIC_TOOL_GRADES |
TEXT,NONE |
a platform-hosted pool row measured at one of these tool-calling grades is a weak tool caller (so is a row in small-model mode) |
app.chat.continuation.tail-chars |
APP_CHAT_CONTINUATION_TAIL_CHARS |
400 |
how much of the end of the reply the wording check reads |
app.chat.continuation.default-locale |
(none) | en |
the language whose nudge is used when none matched |
app.chat.continuation.intent.en, app.chat.continuation.closing.en, app.chat.continuation.done.en, app.chat.continuation.asks.en, app.chat.continuation.unfinished-ending.en (one key per language tag: .es, .hi, ...) |
(none) | regular expressions in application.yml (English) |
the wording check: the model says what it will do (last sentence only) / an offer of further help / finished or a summary / ends with a question / stops where a list was about to start. Case-insensitive, Unicode |
app.chat.continuation.nudge.en (and one key per further language tag) |
(none) | the shipped prompt nudge.continue_unfinished |
Deprecated alias. The nudge is the prompt-registry key nudge.continue_unfinished (Admin > Instructions > All prompts; one file per language under prompts/<lang>/). A value set here that differs from the shipped text is applied as that prompt's PLATFORM-scope default and logged as deprecated at startup; a stored override (agent, workspace, platform) always beats it. A language with no entry uses the default-locale entry, then the shipped text. Not set in application.yml |
app.routing.simple.max-chars |
APP_ROUTING_SIMPLE_MAX_CHARS |
160 |
a longer message is never simple, so never sent to the economy model by the heuristic |
app.routing.simple.max-clauses |
APP_ROUTING_SIMPLE_MAX_CLAUSES |
2 |
more sentences than this is never simple |
app.routing.simple.clause-boundary |
(none) | regular expression in application.yml |
where one sentence ends and the next begins (ASCII terminators need a space after them; the full stops of Chinese, Japanese, Hindi and Arabic do not) |
app.routing.simple.non-latin-simple |
APP_ROUTING_SIMPLE_NON_LATIN_SIMPLE |
false |
false: a message with letters outside the Latin script is never simple; true: it is judged by length, sentences and the signal words configured for its language |
app.routing.simple.accented-latin-simple |
APP_ROUTING_SIMPLE_ACCENTED_LATIN_SIMPLE |
false |
false: a message with accented Latin letters (Spanish, French, German, Vietnamese, ...) is never simple, because the shipped signal words are English and cannot say whether it is a hard question; true: it is judged by length, sentences and the signal words configured for its language. A plain-ASCII message in another Latin-script language is still judged by the English words: add that language's signal words to cover it |
app.routing.simple.signal-words.en (and one key per further language tag) |
(none) | English list in application.yml |
comma-separated phrases that mean analysis, generation or multi-step work |
app.tools.auto-selection.unscorable-uses-full-budget |
TOOL_AUTO_SELECTION_UNSCORABLE_USES_FULL_BUDGET |
true |
a message with letters outside the Latin script that matches no tool gets max-tools-per-turn tools instead of no-match-max-tools |
app.chat.resilience.thought-signature-degrade |
APP_CHAT_RESILIENCE_THOUGHT_SIGNATURE_DEGRADE |
false |
Gemini refuses the request that follows a tool call when the model's own function call goes back without its thought signature. The platform sends it back intact, so this should not happen; if the provider refuses anyway the error is reported (default). true is a last resort: the turn is answered once more WITHOUT tools: the reply opens with thought-signature-notice (which steps had already run), the model is shown what the tools returned (thought-signature-evidence-note, at most thought-signature-evidence-chars characters, default 8000), the tools are not run again, and the reply is not cached. Counted in chat.degraded.tools_disabled |
app.chat.resilience.thought-signature-evidence-chars |
APP_CHAT_RESILIENCE_THOUGHT_SIGNATURE_EVIDENCE_CHARS |
8000 |
the most characters of tool results shown to the model in that reply |
app.chat.resilience.thought-signature-notice / thought-signature-evidence-note |
(none) | text in application.yml |
shown to the person at the start of that reply ({tools} = steps already run) / model-facing ({evidence} = what the tools returned) |
The request to a hosted agent-turn client (POST /api/completions/agent-turn) is stateless and does not carry the signatures back to the platform, so that endpoint is not covered by the signature fix.
The model of background calls: compaction, session summary, assist, judges¶
When a workspace uses its own provider key, four background calls need a model that is not the agent's answer: the summary that context compaction writes, the rolling summary of a long session, the "Ask AI" assist and judges. They used to take the first model of the first active credential's catalog listing, which is alphabetical, so what they cost and how well they summarised followed sort order. The model is now chosen in this order, and the first usable choice wins; the one used is logged at INFO (Auxiliary model for <purpose>: <provider> <model> (chosen by <layer>)).
- The workspace's own AI Model Control preference for the purpose (
assist,eval-judge; the workspace default row, applied by the gateway when it names the provider in use, covers the others). - The platform choice,
app.auxiliary-model.<purpose>.providerand.model. A provider without a model means that provider's catalog default model; a model without a provider is ignored and logged once. A provider with no active credential in the workspace is skipped. - The provider and model of the agent the call is made for (
use-agent-model). Compaction always knows its agent; the session summary does too when the caller passes it, and otherwise goes to step 4. - The workspace's best active credential and that provider's catalog default model (the same explicit, reviewable choice new agents get), never the first alphabetical entry.
Workspaces on the platform pool are not affected: the pool picks its own model.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.auxiliary-model.use-agent-model |
APP_AUXILIARY_MODEL_USE_AGENT_MODEL |
true |
false skips step 3: a call made for an agent no longer follows that agent's model |
app.auxiliary-model.compaction.provider, app.auxiliary-model.compaction.model |
APP_AUXILIARY_MODEL_COMPACTION_PROVIDER, APP_AUXILIARY_MODEL_COMPACTION_MODEL |
empty | provider (a ProviderName, for example GEMINI) and model of the summary context compaction writes |
app.auxiliary-model.session-summary.provider, app.auxiliary-model.session-summary.model |
APP_AUXILIARY_MODEL_SESSION_SUMMARY_PROVIDER, APP_AUXILIARY_MODEL_SESSION_SUMMARY_MODEL |
empty | the same for the rolling summary of a long session |
app.auxiliary-model.assist.provider, app.auxiliary-model.assist.model |
APP_AUXILIARY_MODEL_ASSIST_PROVIDER, APP_AUXILIARY_MODEL_ASSIST_MODEL |
empty | the same for the "Ask AI" assist and the studio's one-off completions |
app.auxiliary-model.judge.provider, app.auxiliary-model.judge.model |
APP_AUXILIARY_MODEL_JUDGE_PROVIDER, APP_AUXILIARY_MODEL_JUDGE_MODEL |
empty | the same for judges and verifiers (the setting and its resolver exist; the quality judges still pick their own model until they are moved onto the resolver) |
Behaviour change from the earlier alphabetical pick: with nothing configured, compaction now runs on the model of the agent being compacted, and a call without an agent runs on the best credential's catalog default model. Set use-agent-model=false or a platform choice to keep a cheaper model for these calls.
Session history, stream versus buffered parity, and response caches¶
Three groups of settings make the agent behave the same whichever way a turn is run: app.chat.tool-ledger (what the model is told about earlier turns' tools), app.chat.parity (where the streamed and the buffered turn deliberately differ) and app.chat.cache (what the response caches may serve and what they key on). app.chat.tools.text-call-recovery.scope (above) decides which models the small-model text-call workaround wraps.
Session history keeps a compact tool ledger. The tool loop runs inside the model client, so history used to carry only the model's prose: on the next turn it could not see the file id a tool had returned, which write calls it had already made, or that an action was still waiting on a person. Each assistant turn that called tools now stores a record with it (ChatTurn.tools, serialised in the same Redis entry under the same tenant-scoped session key): one entry per call with the tool, an arguments summary, a result summary, a status (ok, failed, not run, awaiting human approval, repeat) and, for a call held for a human, the approval request id. Arguments and results are masked like the tool-call log and the model-facing tool result (credentials, and personal data when the tenant's gateway policy asks), compacted to one line and cut to the limits below. The stored reply text is untouched, so the transcript, exports and the failed/interrupted markers read as before; only the model's view of history folds the record in, ahead of the reply, as plain text (a replayed tool-use/tool-result pair would need provider ids and thought signatures that a summary cannot recreate). The newest replay-turns assistant turns that carry a record show it; the history window's character and token limits are measured on the text the model will be sent, so a bulky record costs the oldest turns, not the newest; turns that leave the window reach the rolling summary with their record in it, and context compaction handles the text like any other. A system note tells the model the record is the platform's, that it counts as tool use in this conversation (the tool-trust rule forbids claiming tool use that did not happen in it, and these calls did), and that it must never write one itself. A reply that failed after tools ran keeps its record and is shown to the model as the record plus a failure note, never as the error text. A call held for approval is recorded as awaiting human approval with its request id; how it was decided is in the approval-outcome note the platform appends to the history when a person decides (the ledger of the turn that parks cannot know it). Editing or regenerating a message keeps the record of the turns before it.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.chat.tool-ledger.enabled |
APP_CHAT_TOOL_LEDGER_ENABLED |
true |
false records and stores nothing: history is text only, as before |
app.chat.tool-ledger.replay-enabled |
APP_CHAT_TOOL_LEDGER_REPLAY_ENABLED |
true |
false still stores the record but does not show it to the model |
app.chat.tool-ledger.max-entries-per-turn |
APP_CHAT_TOOL_LEDGER_MAX_ENTRIES_PER_TURN |
20 |
entries kept for one turn; calls past it are counted ("...and N more") |
app.chat.tool-ledger.max-argument-chars / max-result-chars |
APP_CHAT_TOOL_LEDGER_MAX_ARGUMENT_CHARS / APP_CHAT_TOOL_LEDGER_MAX_RESULT_CHARS |
160 / 200 |
an entry's arguments and result are cut to this many characters; at most 500 (the credential scrub's own cap) |
app.chat.tool-ledger.max-replay-chars-per-turn |
APP_CHAT_TOOL_LEDGER_MAX_REPLAY_CHARS_PER_TURN |
2000 |
the rendered block of one turn is cut to this many characters |
app.chat.tool-ledger.replay-turns |
APP_CHAT_TOOL_LEDGER_REPLAY_TURNS |
6 |
only the newest this-many assistant turns that carry a record show it; older ones are left to the rolling summary and compaction |
app.chat.tool-ledger.cut-marker, header, line, detail, approval-detail, omitted-line, status-ok, status-failed, status-not-run, status-awaiting-approval, status-replayed, failed-reply-note, trust-note |
(none) | text in application.yml |
model-facing wording of the record and of the note that tells the model it may rely on it. {tool} {arguments} {status} {detail} {result} {approval} {count} are filled in by the code |
Streamed and buffered turns behave the same except where it is a setting. Both paths share one continuation policy for a reply that stops at an announced step (the person's message is never edited to nudge the model; the real conversation is replayed), the provider's finish-reason handling, the thought-signature fallback, the failover replay gate, the tool-call ledger, and one stable tool order (the buffered depths used the resolution order, so the tools and the prompt-cache prefix changed with the reasoning depth). The differences that remain, and why:
| Difference | Why it exists | Setting |
|---|---|---|
| A FAST turn that offers a generation tool or an MCP tool streams its words live but holds the part from the first link on until the reply is complete | a fabricated result link must be replaced before it reaches the person; it cannot be recalled once streamed | app.chat.parity.fast-buffering-triggers (GENERATION_TOOLS, MCP_TOOLS; empty = never hold), app.chat.parity.fast-buffering-mode (WHOLE_REPLY = hold the whole reply) |
| Output guardrails withhold a buffered reply but only audit a streamed one | the tokens of a streamed reply have already been delivered | add OUTPUT_GUARDRAILS to fast-buffering-triggers: a FAST turn of an agent with output guardrails is then held, a violating reply is withheld and the replacement is what is stored. Pattern-based guardrails only on FAST (the semantic toxicity check is a blocking model call); the buffered depths run it too |
| The buffered verifiers (self-consistency, Reflexion) rewrite a flagged answer without tools | re-running the tools could repeat a side effect | the rewrite is accepted only if it is not a fraction of the answer it replaces, and the evidence it is shown is bounded: app.chat.parity.verifier-min-revision-ratio, verifier-ratio-applies-above-chars, verifier-max-evidence-chars |
| Reasoning depth itself (verification passes, planning, no live streaming) | that is what CAREFUL, RIGOROUS and PLANNED are for | the agent's reasoning depth |
| No difference: the turn journal records every depth (FAST, CAREFUL, RIGOROUS, PLANNED) and delegated or specialist turns, with approval events | the journal is written by the shared turn code, not by one path | app.turn-journal.enabled (off by default); old turns are purged by app.turn-journal.retention-days (default 90, 0 = keep) |
Small-model workarounds (text-call recovery, its hold-back of a reply starting with {) |
only a weak tool caller needs them | app.chat.tools.text-call-recovery.scope (above) |
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.chat.parity.fast-buffering-triggers |
APP_CHAT_PARITY_FAST_BUFFERING_TRIGGERS |
GENERATION_TOOLS,MCP_TOOLS |
what makes a FAST turn buffer its reply instead of streaming it |
app.chat.parity.fast-buffering-mode |
APP_CHAT_PARITY_FAST_BUFFERING_MODE |
FROM_FIRST_LINK |
how a turn that matches a trigger reaches the person: FROM_FIRST_LINK streams it live and holds only the part from the first link on until the reply is complete; WHOLE_REPLY holds all of it (the behaviour before 2026-10-23). An OUTPUT_GUARDRAILS hold is always whole |
app.chat.parity.fast-hold-markers |
APP_CHAT_PARITY_FAST_HOLD_MARKERS |
http,www. |
text that starts the held part, ignoring case; the invented-link guard's host markers are always added. Keep it covering every link shape the guard detects |
app.chat.parity.verifier-max-evidence-chars |
APP_CHAT_PARITY_VERIFIER_MAX_EVIDENCE_CHARS |
12000 |
the buffered verifiers see the newest this-many characters of the turn's tool results; 0 = all |
app.chat.parity.verifier-min-revision-ratio |
APP_CHAT_PARITY_VERIFIER_MIN_REVISION_RATIO |
0.3 |
a verifier's rewrite shorter than this fraction of the answer is not accepted; 0 accepts any |
app.chat.parity.verifier-ratio-applies-above-chars |
APP_CHAT_PARITY_VERIFIER_RATIO_APPLIES_ABOVE_CHARS |
400 |
the ratio check applies to answers longer than this |
Response caches are keyed on the conversation and closed to tool turns. The exact-match cache (caching on the agent) and the semantic cache (semantic caching) are opt-in per agent. They keyed on the single message and served and stored any turn, so "yes" or "continue" returned the answer cached for a different conversation, and an answer built from live tool data could be served to the next asker. Now the key includes a digest of the newest context-turns turns before the message (a first message keys on the message alone, so an FAQ still hits; the semantic cache folds the same digest into the row's subject_hash, so rows stored before keep matching a first message), and an agent that offers tools is not cacheable at all: its turns are neither read from nor written to either cache, and a turn whose model called a tool is never stored. Branching's regenerate drops the entry of the conversation it regenerates. The workflow step cache is a separate, workflow-level opt-in and is unchanged.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.chat.cache.eligible-with-tools |
APP_CHAT_CACHE_ELIGIBLE_WITH_TOOLS |
false |
true lets the caches serve and store the turns of an agent that offers tools (the old behaviour; only for tools whose results never change) |
app.chat.cache.key-on-conversation |
APP_CHAT_CACHE_KEY_ON_CONVERSATION |
true |
false keys on the message alone, as before |
app.chat.cache.context-turns |
APP_CHAT_CACHE_CONTEXT_TURNS |
2 |
how many of the newest earlier turns go into the key |
Limits that used to be constants (package 3c-1)¶
Which of these a workspace can change without the operator. A property listed here that is an owner-visible product control
is also a settings catalog key (an admin changes it in Admin > Runtime settings; the property stays the platform value, the
default, and the pinned value when the key is locked): app.guardrails.step-limit.{grace-calls,supervised-divisor,supervised-min-steps,autonomous-multiplier},
app.chat.semantic-cache.*, app.chat.follow-up.{max-sticky-tools,keep-primary-model}, app.chat.tool-ledger.* (workspace-wide
keys, not per agent), app.chat.limits.attachments.* (the catalog value is whole megabytes; the upload is still capped by
spring.servlet.multipart.max-file-size), app.memory.limits.{tenant-max-memories,edge-memory-max-facts} and
app.skills.prompt.{max-skills,max-block-chars} (workspace-wide). The background model of each purpose is chosen per workspace on
the AI Model Control page. The decision for every other property of this work, with the reason, is the triage table in
settings.md.
These numbers were static final constants of the turn, memory, skills and context classes. Each is now a typed setting
whose default is the old constant, so nothing changes until an operator sets one. The code default and the value in
application.yml are pinned together by a test. Not every value is a free choice: a few are bounds that other limits
lean on (the notes say which).
Memory (app.memory.limits.*)
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.memory.limits.agent-max-memories |
APP_MEMORY_LIMITS_AGENT_MAX_MEMORIES |
50 |
long-term memories one agent keeps across every subject; the oldest are pruned past it |
app.memory.limits.tenant-max-memories |
APP_MEMORY_LIMITS_TENANT_MAX_MEMORIES |
300 |
shared memories one tenant keeps (memory blocks excluded); the oldest are pruned past it |
app.memory.limits.agent-recall-top-k |
APP_MEMORY_LIMITS_AGENT_RECALL_TOP_K |
5 |
memories an agent's automatic recall puts into a turn |
app.memory.limits.tenant-recall-top-k |
APP_MEMORY_LIMITS_TENANT_RECALL_TOP_K |
5 |
shared memories a turn's automatic recall puts into it |
app.memory.limits.session-max-stored-entries |
APP_MEMORY_LIMITS_SESSION_MAX_STORED_ENTRIES |
400 |
turns kept in a session's Redis list: the hard storage bound, and the ceiling of the replay window app.chat.history-max-turns (the platform setting that edits the window still validates against 400; see the guide's note on history) |
app.memory.limits.session-fold-batch |
APP_MEMORY_LIMITS_SESSION_FOLD_BATCH |
40 |
out-of-window turns folded into the rolling summary at a time (one summarisation call per this many); used as at most session-max-stored-entries - 1 |
app.memory.limits.edge-memory-max-facts |
APP_MEMORY_LIMITS_EDGE_MEMORY_MAX_FACTS |
24 |
facts the edge (on-device) memory endpoint returns; they go into a 4k to 8k context window |
Skills in the prompt and skill distillation (app.skills.prompt.*, app.skills.distillation.*)
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.skills.prompt.max-skills |
APP_SKILLS_PROMPT_MAX_SKILLS |
60 |
skills listed in the system prompt; the rest are counted ("N more skills are installed but not listed here") |
app.skills.prompt.max-block-chars |
APP_SKILLS_PROMPT_MAX_BLOCK_CHARS |
9000 |
character budget of the skill index (about 2,300 tokens); entries keep their description while it fits |
app.skills.prompt.names-only-headroom |
APP_SKILLS_PROMPT_NAMES_ONLY_HEADROOM |
2000 |
extra room, past the budget, that bare names may still use before the list is cut |
app.skills.prompt.max-description-words |
APP_SKILLS_PROMPT_MAX_DESCRIPTION_WORDS |
30 |
words of a skill's description kept in the index (long enough to keep a "Use when..." trigger) |
app.skills.prompt.history-turns |
APP_SKILLS_PROMPT_HISTORY_TURNS |
6 |
earlier user messages the sticky "skills stay on" gate remembers per session |
app.skills.prompt.max-tracked-sessions |
APP_SKILLS_PROMPT_MAX_TRACKED_SESSIONS |
2000 |
sessions the gate tracks before the least recently used are forgotten |
app.skills.distillation.min-distinct-runs |
APP_SKILLS_DISTILLATION_MIN_DISTINCT_RUNS |
3 |
distinct sessions a tool sequence must appear in before it can be distilled |
app.skills.distillation.min-sequence-length |
APP_SKILLS_DISTILLATION_MIN_SEQUENCE_LENGTH |
2 |
sequences shorter than this are ignored |
app.skills.distillation.max-sequence-length |
APP_SKILLS_DISTILLATION_MAX_SEQUENCE_LENGTH |
8 |
sequences longer than this are ignored |
app.skills.distillation.max-jobs-scanned |
APP_SKILLS_DISTILLATION_MAX_JOBS_SCANNED |
500 |
most recent succeeded jobs read per pass |
app.skills.distillation.max-episodes-scanned |
APP_SKILLS_DISTILLATION_MAX_EPISODES_SCANNED |
500 |
most recent rewarded episodes read per pass |
app.skills.distillation.min-episode-reward |
APP_SKILLS_DISTILLATION_MIN_EPISODE_REWARD |
0.60 |
an episode counts as a success when its reward is at least this |
app.skills.distillation.max-proposals-per-run |
APP_SKILLS_DISTILLATION_MAX_PROPOSALS_PER_RUN |
5 |
draft skills staged per pass; the rest are reported as skipped |
app.skills.distillation.max-sample-goals |
APP_SKILLS_DISTILLATION_MAX_SAMPLE_GOALS |
3 |
example goals kept per pattern |
app.skills.distillation.max-goal-chars |
APP_SKILLS_DISTILLATION_MAX_GOAL_CHARS |
160 |
characters of one example goal |
Small-model mode (app.chat.small-model.*): how the few tools of a weak tool caller are chosen.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.chat.small-model.tool-cap |
APP_CHAT_SMALL_MODEL_TOOL_CAP |
6 |
tools offered on one small-model turn |
app.chat.small-model.browser-family-quota |
APP_CHAT_SMALL_MODEL_BROWSER_FAMILY_QUOTA |
4 |
tools a browsing request keeps (navigate, read, click and type only work as a set) |
app.chat.small-model.intent-boost |
APP_CHAT_SMALL_MODEL_INTENT_BOOST |
50 |
score of a tool that serves a signalled intent; beats word overlap, loses to a tool named in the message |
app.chat.small-model.named-tool-score |
APP_CHAT_SMALL_MODEL_NAMED_TOOL_SCORE |
1000 |
score of a tool the message names outright |
Local models (app.chat.local-model.*, app.providers.small-model-max-billions): how a turn adapts to a model served on the person's own machine. Such a model reads its prompt at a few hundred tokens a second on a laptop GPU and far slower on a CPU, and its server keeps one prompt cache with the tool definitions ahead of the system prompt, so an unchanged prompt start is what makes the next turn fast. See local-models.md.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.chat.local-model.stable-tools |
APP_CHAT_LOCAL_MODEL_STABLE_TOOLS |
true |
offer a local model (the install's Ollama pool row, or a provider in providers) the agent's tools unchanged and in a fixed order on every turn instead of a per-message selection, so every turn after the first is answered from the server's cache and a tool the message did not name (web search) is never missing. A model in small-model mode keeps its short list |
app.chat.local-model.providers |
APP_CHAT_LOCAL_MODEL_PROVIDERS |
OLLAMA,LOCAL_OPENAI |
bring-your-own providers that are servers on the person's machine |
app.providers.small-model-max-billions |
APP_PROVIDERS_SMALL_MODEL_MAX_BILLIONS |
8.0 |
a pool row whose model has this many billions of parameters or fewer runs in small-model mode unless its "Small-model mode" control says otherwise; negative = never by size |
Turn limits (app.chat.limits.*, app.chat.semantic-cache.*, and additions to app.chat.parity.* and app.chat.resilience.*)
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.chat.limits.escalation.low-confidence-max-chars |
APP_CHAT_LIMITS_ESCALATION_LOW_CONFIDENCE_MAX_CHARS |
400 |
a low-confidence marker counts only in a reply shorter than this (see app.chat.escalation.*) |
app.chat.limits.escalation.low-confidence-leading-chars |
APP_CHAT_LIMITS_ESCALATION_LOW_CONFIDENCE_LEADING_CHARS |
240 |
... and only within the first this-many characters of it |
app.chat.limits.escalation.registration-ttl |
APP_CHAT_LIMITS_ESCALATION_REGISTRATION_TTL |
PT10M |
how long a cheap client stays registered as escalatable |
app.chat.limits.attachments.max-image-bytes |
APP_CHAT_LIMITS_ATTACHMENTS_MAX_IMAGE_BYTES |
8388608 |
largest chat image (8 MiB) |
app.chat.limits.attachments.max-audio-bytes |
APP_CHAT_LIMITS_ATTACHMENTS_MAX_AUDIO_BYTES |
15728640 |
largest chat audio file (15 MiB) |
app.chat.limits.attachments.max-video-bytes |
APP_CHAT_LIMITS_ATTACHMENTS_MAX_VIDEO_BYTES |
26214400 |
largest chat video (25 MiB) |
app.chat.limits.attachments.max-document-bytes |
APP_CHAT_LIMITS_ATTACHMENTS_MAX_DOCUMENT_BYTES |
10485760 |
largest document or spreadsheet (10 MiB); the inline path's own ceiling is separate |
app.chat.limits.directive-cache.ttl |
APP_CHAT_LIMITS_DIRECTIVE_CACHE_TTL |
PT30S |
Superseded by app.prompts.cache.ttl: the prompt registry now caches the overrides and nothing reads this key |
app.chat.limits.directive-cache.failure-ttl |
APP_CHAT_LIMITS_DIRECTIVE_CACHE_FAILURE_TTL |
PT5S |
Superseded by app.prompts.cache.failure-ttl: the prompt registry now caches the overrides and nothing reads this key |
app.chat.limits.directive-cache.max-tenants |
APP_CHAT_LIMITS_DIRECTIVE_CACHE_MAX_TENANTS |
10000 |
Superseded by app.prompts.cache.max-tenants: the prompt registry now caches the overrides and nothing reads this key |
app.chat.limits.share-turn-timeout |
APP_CHAT_LIMITS_SHARE_TURN_TIMEOUT |
PT45S |
how long a visitor of a shared agent waits for the reply of one turn |
app.chat.limits.economy-aux-min-tokens |
APP_CHAT_LIMITS_ECONOMY_AUX_MIN_TOKENS |
256 |
the economy model's auxiliary calls (such as the swarm goal verifier) get the agent's max tokens, but at least this |
app.chat.limits.economy-aux-max-tokens |
APP_CHAT_LIMITS_ECONOMY_AUX_MAX_TOKENS |
1024 |
... and at most this |
app.chat.limits.completed-event-excerpt-chars |
APP_CHAT_LIMITS_COMPLETED_EVENT_EXCERPT_CHARS |
500 |
characters of the answer carried by the "agent message completed" workflow event (tenant webhooks) |
app.chat.semantic-cache.similarity-threshold |
APP_CHAT_SEMANTIC_CACHE_SIMILARITY_THRESHOLD |
0.95 |
cosine similarity a stored question needs to be served for a new one (the keys were read before; they are now in application.yml) |
app.chat.semantic-cache.ttl-hours |
APP_CHAT_SEMANTIC_CACHE_TTL_HOURS |
6 |
hours a stored answer is served |
app.chat.semantic-cache.max-entries-per-agent |
APP_CHAT_SEMANTIC_CACHE_MAX_ENTRIES_PER_AGENT |
200 |
stored answers kept per agent |
app.chat.parity.verifier-history-window |
APP_CHAT_PARITY_VERIFIER_HISTORY_WINDOW |
6 |
earlier messages the buffered verifiers see as grounding |
app.chat.resilience.structured-call-timeout |
APP_CHAT_RESILIENCE_STRUCTURED_CALL_TIMEOUT |
PT2M |
how long a structured (typed input and output) agent call, such as a workflow node, waits for the reply |
app.chat.resilience.hosted-self-heal-cooldown |
APP_CHAT_RESILIENCE_HOSTED_SELF_HEAL_COOLDOWN |
PT10M |
least time between two repair attempts for the same hosted-pool row whose model id the provider reports gone |
The prompt registry's own settings, app.prompts.*, are in "Prompt registry" further down; see prompts.md.
Context compaction estimates (app.context.compaction.*, next to the keys documented with the context settings)
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.context.compaction.message-overhead-tokens |
APP_CONTEXT_COMPACTION_MESSAGE_OVERHEAD_TOKENS |
4 |
tokens every message costs beyond its text |
app.context.compaction.media-tokens |
APP_CONTEXT_COMPACTION_MEDIA_TOKENS |
1000 |
tokens one attached image, audio or video costs |
app.context.compaction.response-overhead-tokens |
APP_CONTEXT_COMPACTION_RESPONSE_OVERHEAD_TOKENS |
6 |
tokens a tool call or tool response costs beyond its name, arguments and text |
app.context.compaction.summary-frame-tokens |
APP_CONTEXT_COMPACTION_SUMMARY_FRAME_TOKENS |
40 |
tokens of a summary message beyond its body |
app.context.compaction.placeholder-tokens |
APP_CONTEXT_COMPACTION_PLACEHOLDER_TOKENS |
48 |
estimated tokens of one cleared-result placeholder; a result smaller than twice this is never cleared |
app.context.compaction.restore-result-max-chars |
APP_CONTEXT_COMPACTION_RESTORE_RESULT_MAX_CHARS |
16384 |
characters of one tool result restore_tool_result returns; the rest is cut with a marker |
app.context.compaction.window-rules |
claude 200000, gemini 200000, gpt-5 / gpt-4o / gpt-4.1 / gpt-4-turbo / o1 / o3 / o4 128000 | context window by model name for a model the provider catalog gives none. Each rule has tokens, contains and starts-with; the first rule matching the lower-cased model id wins; a list set here replaces the shipped one. These are conservative floors and differ from the figures in the model seed catalog (Gemini 1M, GPT-5 400k); that mismatch is known and was not changed here |
Evals, the Agent Lab, the quality judge and RL exploration on the real pipeline¶
An eval replay used to reach the chat service through a reflective bridge and ran text-only (chatTextOnly), so a green eval said little about how the agent behaves with its tools. A run now replays through the same turn pipeline a person's message takes (ReplayTurnRunner, in agents-turn; the typed bridge AgentChatServiceTurnReplayer in app hands it to the eval runner), in one of two modes, shown on every run in the console and in its API response (mode):
| Mode | What the run measures |
|---|---|
FULL (the default) |
The production pipeline in the eval sandbox. Same reasoning depth, tool selection, guardrails, argument policies, output caps, step ceiling and approval gate as a person's message, and the run consumes its budget cap. A tool runs for real only if its classification says it only reads (see below); every other call is answered with a simulated result. A call that needs a person is held exactly as in production (the case sees an unanswered approval), but no approval request is recorded, so nothing reaches an approver. |
QUICK |
Text only. The agent's tool set is withheld; the model can only answer. Cheaper and faster, and it says nothing about tool use. Budget, moderation, memory and the kill switch still apply. Chosen explicitly. |
Which mode a run takes, first match wins: the mode named when the run is triggered (POST .../runs, {"mode":"QUICK"}; the console's run options), else the mode its dataset pins (mode on the dataset: FULL, QUICK, or none), else the workspace's setting quality.eval.defaultMode (a workspace admin can switch the default back to quick), else the platform property app.quality.eval.default-mode (full). The mode is frozen on the run row (eval_mode, V670), so a finished run always says what it measured. Datasets that held the old default (V670 turned their tool_execution_enabled from FALSE into "no pin") follow the new default.
What may really run in a FULL run. The decision is made per call from the tool's classification (tool_classification, never from the tool's name), and it fails closed:
- A tool runs for real only if it is declared a reader (
READ_PUBLIC,READ_TENANT, orCREDENTIALEDfor a read with a stored credential) and is neitherDURABLEnor irreversible.http_requestalso needs a read method (GET, HEAD, OPTIONS). - Everything else is simulated: the tool is never reached, the model is told the call was accepted and nothing was changed or sent (
toolresult.eval_sandbox.simulatedin the prompt registry), the ledger and the turn journal record the call asSIMULATED/simulated, the case stores it with statusSIMULATED, and the run detail lists the simulated tools (simulatedTools) as the part of the agent's tool use the run could not really exercise. That covers writes (memory, knowledge, data tables, workspace files), everything that leaves the tenant (mail, calendar, channels, webhooks, uploads), shell, code and scheduling, payments, and delegation to other agents (a delegated agent runs its own turn, outside this turn's sandbox). - A tool with no classification (an MCP or custom tool nobody classified), a quarantined one, and a classification that cannot be read are simulated too. A classification derived only from an MCP server's own
readOnlyHintdoes not count: it is a claim by a third party. To let an unclassified reader run, classify it, or list it inapp.quality.eval.sandbox.read-only-tools; that list never overrides a declared classification. - A
FULLreplay refuses to start when the effect policy is not available: running the agent's tools unsandboxed is never a fallback.
Every call of a FULL case is reported with the status the turn itself recorded for it (ran, failed, not run, held for approval, replayed, simulated), not read from the wording of its output; trajectory evals assert on that record, and a simulated call counts as made (the agent chose the tool; only its effect was withheld). The status is stored with the case's tool calls and shown in the run detail.
Approvals in an eval auto-resolve only through the sandbox auto-approval (the dataset's autoApproveTools, below). Without it a gated call is held, as in production. With it, a held call is approved by the eval sandbox, counted per case, and journaled as decided:eval-approved (never as a person's decision); the approved tool is still subject to the effect rule above, so an approved write is simulated, not executed.
Auto-approval is a sandbox, not a shortcut. It skips only the "needs a person, record it and stop" step. The dispatch gate, procedure rules, the verifier, the step ceiling, argument policies, output caps and guardrails all still apply. It is granted to one turn at a time under a request id the server makes, so a chat request cannot ask for it; it is counted per case; the run row is flagged auto_approved (V667) and says so in its API response and export bundle; and it is checked against the operator's list when a dataset sets it, when a FULL run is triggered, and again at every replay, so removing a tenant from the list stops a run that is already queued. A QUICK run has no tool calls, so it neither uses nor needs it. A dataset with auto-approval has a different content hash from the same dataset without it, so a run of one cannot satisfy the eval gate for the other.
| Property | Environment variable | Default | Effect |
|---|---|---|---|
app.quality.eval.default-mode |
EVAL_DEFAULT_MODE |
full |
The mode of an eval run that names none and whose dataset pins none. full = the production pipeline in the eval sandbox; quick = the agent's tools are withheld, text only. A workspace overrides it with the setting quality.eval.defaultMode (a non-security setting, tenant scope); a run can always name its own mode. Replaces app.quality.eval.default-tool-execution |
app.quality.eval.sandbox.read-only-tools |
EVAL_SANDBOX_READ_ONLY_TOOLS |
empty | Tool names or * globs the operator vouches are read-only, so they run for real in a FULL run although nobody classified them (an MCP or custom tool). Applies only to a tool with no declared classification; it can never make a declared write, send or delete run. Empty = every unclassified tool is simulated |
app.quality.eval.auto-approve.tenant-ids |
EVAL_AUTO_APPROVE_TENANT_IDS |
empty | Comma-separated tenant ids the operator declares non-production. Only these tenants may set autoApproveTools, trigger a run that uses it, or have such a run replay. Empty = no tenant. There is no per-tenant "production" flag to infer this from, so it is an explicit list |
app.quality.eval.auto-approve.actor |
EVAL_AUTO_APPROVE_ACTOR |
eval-auto-approve |
The name the auto-approvals are attributed to in logs (never a person) |
app.quality.eval.auto-approve.excluded-tools |
EVAL_AUTO_APPROVE_EXCLUDED_TOOLS |
local_shell,coding_task,schedule_task,a2a_call_agent,run_in_code_workspace,apply_code_changes,*payment*,*refund*,*charge*,*transfer*,*delete* |
Tool names or * globs that are never auto-approved: a gated call to one still parks, whatever the dataset says |
app.quality.trajectory-eval.enabled |
QUALITY_TRAJECTORY_EVAL_ENABLED |
false |
Trajectory specs (a case grader_spec of type trajectory). Still opt-in. When on, a case whose dataset replays text-only reports its TRAJECTORY dimension as UNAVAILABLE (it made no tool calls) and is neither passed nor failed on it, instead of the spec being ignored silently |
app.agent-lab.with-tools.enabled |
AGENT_LAB_WITH_TOOLS_ENABLED |
false |
Lets an Agent Lab run ask for the agent's real tools (withTools on the run request): the answers then come from the chat turn's tool pipeline, and a call that needs a person parks as a pending approval for the lab's session. false = the lab stays one tool-less completion and a run asking for tools is refused with a 400. Lessons, skill learning, the score judge and the coding seams stay tool-less whatever this says |
app.llm.judge.sample-rate |
APP_LLM_JUDGE_SAMPLE_RATE |
0.05 |
The fraction of live turns the background quality judge samples (monitoring only; it never changes a reply). Also settable at run time, without a restart, through the platform setting platform.quality.judge.sample-rate (0 to 1, the admin settings API): a stored value overrides this one, an unset or unparseable one falls back to it, and the result is clamped to 0..1 |
app.llm.judge.sample-rate-cache-ms |
(none) | 10000 |
How long a read of that platform setting is reused (it is read on every turn) |
app.llm.judge.max-excerpt-length |
(none) | 600 |
Characters of the user message and answer echoed into the judge prompt and the recorded issue |
rl.selection.server-turn-ref |
RL_SELECTION_SERVER_TURN_REF |
false |
Which chat turns the bandits (model route, tool subset, prompt variant, generation parameters, reasoning depth) may vary. false = only a turn whose client sent a turnRef, the Playground, so exploration stays on test traffic. true = the console chat endpoint also gives an untagged turn a srv: reference of its own, so exploration reaches real conversations. Only that endpoint: agent jobs, evals, the lab, channels and an approval's continuation turn never explore. A client does not know a server-made reference, so a thumbs-up cannot reward it |
Settings that do not exist¶
Some recent features are controlled per tenant through the API, not by a deployment setting. There is
no property or environment variable for: the WhatsApp policy guard (declared task, opt-in records,
recipient back-off; see WhatsApp policy), or the DPDP-oriented consent
gate, purpose tags and per-contact export and erasure (/api/tenant/privacy/**, see
DPDP obligations). The only deployment switch for the privacy work is
the nightly retention job (PRIVACY_RETENTION_ENABLED, PRIVACY_RETENTION_CRON, in
Scale and reliability limits). The WhatsApp template send path has one
switch, WHATSAPP_TEMPLATES_ENABLED.
Workflows¶
| Property | Env | Default |
|---|---|---|
app.workflow.execution-engine |
WORKFLOW_EXECUTION_ENGINE |
legacy (in-process); or temporal |
app.workflow.temporal.target |
TEMPORAL_TARGET |
127.0.0.1:7233 |
app.workflow.temporal.namespace |
TEMPORAL_NAMESPACE |
default |
app.workflow.temporal.run-timeout-seconds |
TEMPORAL_RUN_TIMEOUT_SECONDS |
600 |
spring.kafka.bootstrap-servers |
WORKFLOW_KAFKA_BOOTSTRAP_SERVERS |
empty (no Kafka client starts) |
app.workflow.schedule.min-interval-seconds |
APP_WORKFLOW_SCHEDULE_MIN_INTERVAL_SECONDS |
60; the smallest gap between two fires of a workflow schedule. PUT /api/workflows/{id}/schedule with a tighter cron is a 400; 0 turns the check off |
app.workflow.poll.enabled |
APP_WORKFLOW_POLL_ENABLED |
true |
app.workflow.poll.tick |
APP_WORKFLOW_POLL_TICK |
60s |
app.workflow.poll.min-interval-seconds |
APP_WORKFLOW_POLL_MIN_INTERVAL_SECONDS |
60 |
app.workflow.poll.max-interval-seconds |
APP_WORKFLOW_POLL_MAX_INTERVAL_SECONDS |
604800 |
app.workflow.poll.max-items-per-poll |
APP_WORKFLOW_POLL_MAX_ITEMS_PER_POLL |
25 |
app.workflow.poll.max-response-bytes |
APP_WORKFLOW_POLL_MAX_RESPONSE_BYTES |
1048576 |
app.workflow.poll.seen-ids-cap |
APP_WORKFLOW_POLL_SEEN_IDS_CAP |
1000 |
app.workflow.poll.failure-backoff-cap |
APP_WORKFLOW_POLL_FAILURE_BACKOFF_CAP |
8 |
app.workflow.poll.connect-timeout |
APP_WORKFLOW_POLL_CONNECT_TIMEOUT |
5s |
app.workflow.poll.response-timeout |
APP_WORKFLOW_POLL_RESPONSE_TIMEOUT |
15s |
temporal also needs the temporal Spring profile; without it core falls back
to the in-process engine. Workers run in the core-worker shape.
The app.workflow.poll.* settings bound the HTTP polling trigger (a Trigger
node's "new items at this endpoint start the workflow" binding). Polled URLs go
through the SSRF guard (app.security.outbound.allowed-private-hosts is the
operator allow-list for legitimate internal targets), redirects are never
followed so configured headers cannot leak cross-origin, and a failed poll
backs off to interval * min(failures + 1, failure-backoff-cap).
Cost and ROI¶
The per-agent cost and ROI view (Cost and ROI) has one setting.
| Property | Env var | Default |
|---|---|---|
roi.budget-alerts.enabled |
ROI_BUDGET_ALERTS_ENABLED |
true |
It turns the ten-minute sweep that raises the 80% and 100% monthly-budget alerts on or off. The
budget itself is the existing app.llm.gateway.budget.* cap; this adds no new limit.
The property is read as @Value("${roi.budget-alerts.enabled:true}") in RoiBudgetAlertSweep; it is not
in application.yml, so the default lives in code. Compose does not pass it through (set it in core.env).
The /api/tenant/roi/** routes themselves have no switch.
Billing gateways¶
Subscriptions and upgrades go through PaymentGatewayClient; three gateways
ship in core/billing — razorpay (always on), stripe, and phonepe
(profile security). Tenants are billed through whichever gateway
app.billing.default-gateway names; a gateway whose credentials are empty
reports itself unconfigured and a checkout attempt fails with an actionable
error instead of an opaque provider 401.
| Property | Env | Default |
|---|---|---|
app.billing.default-gateway |
DEFAULT_PAYMENT_GATEWAY |
razorpay |
app.billing.stripe.secret-key |
STRIPE_SECRET_KEY |
empty (gateway off) |
app.billing.stripe.publishable-key |
STRIPE_PUBLISHABLE_KEY |
empty |
app.billing.stripe.webhook-secret |
STRIPE_WEBHOOK_SECRET |
empty — webhook verification fails closed without it |
app.billing.stripe.price-id-premium / -unlimited |
STRIPE_PRICE_ID_PREMIUM / STRIPE_PRICE_ID_UNLIMITED |
empty (plan name fails loudly at checkout) |
app.billing.stripe.api-base-url |
STRIPE_API_BASE_URL |
https://api.stripe.com |
app.billing.razorpay.key-id / key-secret / webhook-secret |
RAZORPAY_KEY_ID / RAZORPAY_KEY_SECRET / RAZORPAY_WEBHOOK_SECRET |
empty |
app.billing.phonepe.client-id / client-secret / merchant-id |
PHONEPE_CLIENT_ID / PHONEPE_CLIENT_SECRET / PHONEPE_MERCHANT_ID |
empty (needs profile security) |
The Stripe client talks to the REST API directly (no stripe-java
dependency): subscription checkout uses the invoice's hosted page, and
webhooks map invoice.paid/payment_failed/customer.subscription.* onto
the platform's billing-event vocabulary. These are core env vars — in Compose
put them in deploy/compose/core.env (see core.env.example), not .env.
Billing UI also needs WEB_BILLING_ENABLED=1.
Observability¶
| Property | Env | Default |
|---|---|---|
management.tracing.sampling.probability |
TRACING_SAMPLING_PROBABILITY |
0.1 (Lite: tracing off) |
management.otlp.tracing.export.enabled |
OTLP_TRACING_EXPORT_ENABLED |
true |
management.otlp.tracing.endpoint |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
Langfuse's OTLP endpoint in the tracing profile |
| OTLP auth header | LANGFUSE_OTEL_AUTH_HEADER |
empty |
logging.structured.format.console |
LOG_FORMAT |
empty = plain-text pattern; ecs, logstash or gelf = one JSON object per line. The shipped compose (CORE_LOG_FORMAT, default ecs) and Helm charts set LOGGING_STRUCTURED_FORMAT_CONSOLE=ecs, which takes precedence |
server.shutdown |
APP_SHUTDOWN_MODE |
graceful: on SIGTERM new requests are refused and in-flight ones (SSE streams, LLM calls) may finish; immediate cuts them |
server.compression.enabled |
APP_SERVER_COMPRESSION_ENABLED |
true: gzip for application/json and application/problem+json responses of at least APP_SERVER_COMPRESSION_MIN_SIZE bytes (default 1024). text/event-stream (SSE) is never compressed. Set false when an ingress already compresses |
spring.lifecycle.timeout-per-shutdown-phase |
SHUTDOWN_PHASE_TIMEOUT |
${app.shutdown.drain-seconds}s = 45s. Keep it below the orchestrator's grace period minus any preStop sleep (Helm core: 90 s grace, 10 s preStop) |
app.shutdown.drain-seconds |
APP_SHUTDOWN_DRAIN_SECONDS |
45 (bare integer, no unit). Feeds the phase timeout above unless SHUTDOWN_PHASE_TIMEOUT is set |
app.shutdown.container-grace-seconds |
APP_SHUTDOWN_CONTAINER_GRACE_SECONDS |
0 (not stated). Set to the platform's termination grace period to get a startup WARN when drain + 10 s does not fit |
In JSON mode every MDC key is a top-level field: requestId, tenantId and
userId (set per request by RequestCorrelationFilter and
TenantContextFilter), plus traceId and spanId when tracing is on.
LLM calls carry OpenTelemetry gen_ai.* attributes (system, model, token
usage, agent and conversation ids). Metrics are on /actuator/prometheus.
API error format¶
| Property | Env | Default |
|---|---|---|
app.errors.problem-details.enabled |
APP_ERRORS_PROBLEM_DETAILS_ENABLED |
true: error bodies are RFC 9457 Problem Details (application/problem+json) that also keep every legacy member. false returns the legacy body only (timestamp, status, error and the handler's extras) as application/json |
app.errors.problem-details.type-prefix |
APP_ERRORS_PROBLEM_DETAILS_TYPE_PREFIX |
urn:vegaduta:problem:. Prefix of the type member; set it to an https:// base only if you publish a page per problem type |
The body shape is described in
architecture/overview.md. Turning
the flag off is a rollback switch for a client that cannot accept
application/problem+json; no bundled client needs it.
API conventions¶
Runtime switches for the API conventions in docs/api/README.md.
| Property | Env | Default |
|---|---|---|
app.rate-limit.api.enabled |
APP_RATE_LIMIT_API_ENABLED |
false. When on, every authenticated /api/** caller (tenant + user or API-key subject) gets a fixed-window request budget, the IETF RateLimit / RateLimit-Policy headers on each response, and 429 Problem Details with Retry-After when it is spent. Counts live in Redis (in-process fallback while Redis is down) |
app.rate-limit.api.requests-per-window |
APP_RATE_LIMIT_API_REQUESTS_PER_WINDOW |
600 requests per caller per window |
app.rate-limit.api.window-seconds |
APP_RATE_LIMIT_API_WINDOW_SECONDS |
60 |
app.api.idempotency.ttl-hours |
APP_API_IDEMPOTENCY_TTL_HOURS |
24. How long a response to a request with Idempotency-Key is kept for replay. Needs Redis; without it the header is accepted but not enforced |
app.api.idempotency.in-flight-seconds |
APP_API_IDEMPOTENCY_IN_FLIGHT_SECONDS |
300. How long a key stays claimed by a request that is still running (a repeat meanwhile gets 409). Raise it if a covered request can legitimately run longer |
app.api.deprecation-policy-url |
APP_API_DEPRECATION_POLICY_URL |
The deprecation policy on GitHub. Target of the Link: <...>; rel="deprecation" header on deprecated routes; point it at your own docs when you host them |
Mail and channels¶
SMTP (SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, read by the
workflow and lite profiles) is used for notifications and email steps. Channel credentials (WhatsApp, Telegram, Slack,
Teams, ...) are configured per tenant in the console and stored encrypted, not
in environment variables.
| Property | Env | Default | |
|---|---|---|---|
app.omnichannel.sms.max-reply-chars |
SMS_MAX_REPLY_CHARS |
1500 |
longest SMS reply sent; longer agent replies are cut at a sentence or word boundary with an ellipsis (Twilio's ceiling is 1,600, clamped). See SMS conversations. |
app.omnichannel.rate-limit.enabled |
(none) | false (true in the personal profile). Master switch for the inbound turn cap (see Personal agent settings) |
|
app.signal.* |
(none) | Signal receive loop and limits: Signal channel | |
app.matrix.sync.enabled |
APP_MATRIX_SYNC_ENABLED |
false. Matrix receives nothing until this is true; see Matrix channel |
|
app.imessage.* |
(none) | iMessage polling and rate limits: iMessage channel | |
app.omnichannel.edge-relay.* |
EDGE_RELAY_* |
enabled | a signed-in browser with an on-device model answers the workspace's SMS and WhatsApp messages; every knob is in Edge relay |
Security hardening settings¶
Core properties added by the pre-release security review
(docs/engineering/SECURITY-HARDENING.md
has the rationale and the behaviour each one changes). They are Spring
properties: set them as environment variables with the relaxed-binding names
below. Compose passes only the first one through (x-core-env); add the others
to core.env or the Helm core.env map when you need them.
| Property | Environment variable | Default |
|---|---|---|
app.security.outbound.allowed-private-hosts |
APP_SECURITY_OUTBOUND_ALLOWED_PRIVATE_HOSTS |
empty; comma-separated host or host:port that tenant-configured outbound calls (BYOK, OpenAPI, A2A, webhooks) may reach on a private address, e.g. ollama:11434. Also needed for a LOCAL_OPENAI server, a Signal signal-cli service, a BlueBubbles Mac, a LAN Matrix homeserver and an email (IMAP) mailbox on a private address |
app.security.outbound.egress-allow-list |
APP_SECURITY_OUTBOUND_EGRESS_ALLOW_LIST |
empty (any public host); comma-separated host or *.suffix. When set, tenant-configured outbound calls (BYOK, OpenAPI, MCP URLs, web fetch, webhooks) may reach only these hosts; the private-address checks still apply and allowed-private-hosts entries are exempt. Platform-wide, not per agent |
app.tenancy.fail-on-missing-context |
APP_TENANCY_FAIL_ON_MISSING_CONTEXT |
true; false restores the shared-schema fallback (logged) |
app.security.jwt.required-audience |
APP_SECURITY_JWT_REQUIRED_AUDIENCE |
empty (off); needs a Keycloak audience mapper first |
app.oauth.state.require-browser-cookie |
APP_OAUTH_STATE_REQUIRE_BROWSER_COOKIE |
false |
app.agent.share.visitor-token-secret |
APP_AGENT_SHARE_VISITOR_TOKEN_SECRET |
empty: derived from APP_ENCRYPTION_KEY |
app.storage.local.legacy-unprefixed-fallback |
APP_STORAGE_LOCAL_LEGACY_UNPREFIXED_FALLBACK |
false |
app.sso.tenant-idp.first-broker-login-flow |
APP_SSO_TENANT_IDP_FIRST_BROKER_LOGIN_FLOW |
first broker login |
app.sharing.default-ttl |
APP_SHARING_DEFAULT_TTL |
P30D for new share links |
app.chat.session-ttl-hours |
APP_CHAT_SESSION_TTL_HOURS |
24; how long a conversation (and its owner record) stays in Redis |
The console's NEXT_PUBLIC_OAUTH_STATE_COOKIE (web) pairs with
app.oauth.state.require-browser-cookie: set both when turning the binding
cookie on.
MCP specification alignment settings¶
Added with MCP specification alignment, which has the rationale and the
requirement-by-requirement status. Spring properties; the first three are in application-mcpserver.yml
(active with the mcpserver profile), the last two in application.yml.
| Property | Environment variable | Default |
|---|---|---|
app.mcpserver.origin.mode |
APP_MCPSERVER_ORIGIN_MODE |
enforce; report logs a disallowed Origin and lets it through, off disables the check. Requests with no Origin header (every non-browser MCP client) are never affected |
app.mcpserver.origin.allowed |
APP_MCPSERVER_ORIGIN_ALLOWED |
empty; comma-separated exact origins (https://app.example.com), no wildcards. The request's own Host is not trusted |
app.mcpserver.oauth.* |
APP_MCPSERVER_OAUTH_ENABLED, _RESOURCE, _ISSUER, _AUTHORIZATION_SERVERS, _JWK_SET_URI, _TENANT_CLAIM, _GRANT_CLAIM |
disabled. OAuth 2.1 access tokens for /mcp, next to the vmcp_ grant keys (which keep working). Needs resource (canonical https URI of /mcp), issuer and jwk-set-uri (each must be https, or http on a loopback host for development; a missing or plain-http non-loopback value fails startup). _AUTHORIZATION_SERVERS (comma-separated) lists the authorization servers advertised in the RFC 9728 Protected Resource Metadata (/.well-known/oauth-protected-resource); blank advertises the issuer The token must carry the tenant claim (tenant_id) and a grant claim (mcp_grant_id, UUID of an enabled grant); its aud must contain resource. Scopes are exact-match, no wildcards; see the alignment doc for the scope table |
app.mcp.tool-pinning.mode |
APP_MCP_TOOL_PINNING_MODE |
warn. off records nothing; warn records a hash of each remote MCP server's tool list, audits a change (mcp.tools.changed) and lists it at GET /api/mcp/tool-pins, agents keep using the server; enforce also removes a changed server's tools from agents until an admin calls POST /api/mcp/tool-pins/{id}/approve |
app.mcp.client.routing-headers.enabled |
APP_MCP_CLIENT_ROUTING_HEADERS_ENABLED |
false; sends Mcp-Method / Mcp-Name on outbound Streamable HTTP POSTs |
Local LLM pool bootstrap¶
Registers a local Ollama (or any OpenAI-compatible local server) as the FREE-tier platform pool at startup, with no manual API calls. Off by default; runs once in the api shape only (the jobs and worker shapes skip it), never fails startup (problems are logged at WARN), and is idempotent: a pool row with the same label or base URL is left untouched. The row is created through the normal pool service (key encrypted with APP_ENCRYPTION_KEY, SSRF guard applied), enabled, ahead of every existing row. A private host such as ollama:11434 must also be listed in app.security.outbound.allowed-private-hosts. If the model is not pulled, the row is still created and a WARN gives the exact ollama pull <model> command.
| Property | Environment variable | Default |
|---|---|---|
app.local-llm.bootstrap.enabled |
APP_LOCAL_LLM_BOOTSTRAP_ENABLED |
false |
app.local-llm.bootstrap.base-url |
APP_LOCAL_LLM_BOOTSTRAP_BASE_URL |
http://ollama:11434 |
app.local-llm.bootstrap.model |
APP_LOCAL_LLM_BOOTSTRAP_MODEL |
qwen3.5:4b (the same default the CLI pulls) |
app.local-llm.bootstrap.label |
APP_LOCAL_LLM_BOOTSTRAP_LABEL |
Local model (Ollama) |
app.local-llm.bootstrap.api-key |
APP_LOCAL_LLM_BOOTSTRAP_API_KEY |
ollama (a placeholder Ollama ignores); set it empty for a keyless row that sends no Authorization header |
app.local-llm.bootstrap.retry-delay-ms |
(none) | 10000; delay between up to 6 attempts while the server is unreachable (Ollama often starts after core) |
Personal agent settings¶
Settings added with personal mode. Every default below leaves an existing install
unchanged (except the six agent-runtime defaults set on 2026-10-23, see Agent and platform feature flags); the personal profile (application-personal.yml) flips several of them, listed first. All are
Spring properties, so the environment variable is the relaxed-binding name (app.auth.mode is
APP_AUTH_MODE). Compose passes only the ones named in deploy/compose/personal.yml; put the rest in
deploy/compose/core.env.
What the personal profile changes¶
| Property | Env | Without personal |
With personal |
|---|---|---|---|
app.auth.mode |
APP_AUTH_MODE |
keycloak |
local |
app.agents.taint.mode |
APP_AGENTS_TAINT_MODE |
observe (off before 2026-10-23) |
enforce |
app.agent-hooks.enabled |
AGENT_HOOKS_ENABLED |
true (false before 2026-10-23) |
false (nobody there authors hooks) |
app.tool-arg-policies.enabled |
TOOL_ARG_POLICIES_ENABLED |
true (false before 2026-10-23) |
false (nobody there authors policies) |
app.skills.supply-chain.enforce |
SKILLS_SUPPLY_CHAIN_ENFORCE |
true (false before 2026-10-23) |
false (scan and pins stay on) |
app.mcp.stdio.sandbox |
APP_MCP_STDIO_SANDBOX |
none |
os (the container stack sets none, see Personal mode) |
app.skills.learning.enabled |
APP_SKILLS_LEARNING_ENABLED |
false |
true |
app.skills.learning.self-check.enabled |
APP_SKILLS_LEARNING_SELF_CHECK_ENABLED |
false |
true |
app.learning.proposals.enabled |
APP_LEARNING_PROPOSALS_ENABLED |
false |
true |
app.learning.lessons.enabled |
APP_LEARNING_LESSONS_ENABLED |
false |
true |
app.learning.preferences.enabled |
APP_LEARNING_PREFERENCES_ENABLED |
false |
true |
app.learning.routines.enabled |
APP_LEARNING_ROUTINES_ENABLED |
false |
true |
app.learning.review.enabled |
APP_LEARNING_REVIEW_ENABLED |
false |
true |
app.edition.tier |
APP_EDITION_TIER |
unset (every edition feature on) | personal (the Compose stack derives it from VEGADUTA_EDITION) |
app.edition.billing-enabled |
APP_EDITION_BILLING_ENABLED |
true |
false |
app.a2a.enabled |
APP_A2A_ENABLED |
true |
false |
resolution.sweep.enabled |
RESOLUTION_SWEEP_ENABLED |
true |
false |
app.scheduled-tasks.delivery-channels |
SCHEDULED_TASKS_DELIVERY_CHANNELS |
the shared list | the shared list without slack and a2a |
app.omnichannel.rate-limit.enabled |
APP_OMNICHANNEL_RATE_LIMIT_ENABLED |
false |
true |
app.embeddings.provider |
APP_EMBEDDINGS_PROVIDER |
sidecar |
auto (cloud key, else Ollama, else the sidecar) |
app.sidecar.enabled |
APP_SIDECAR_ENABLED |
true |
false; the launcher's --profile rag sets it to true. With Compose, put an override in deploy/compose/.env, not core.env |
The profile also sets tracing off, app.storage.type=local, app.workflow.execution-engine=legacy, a 2 s Redis
timeout, Hikari pool DB_HIKARI_MAX_POOL_SIZE (10) and DB_HIKARI_MIN_IDLE (2), and the mail and Telegram
blocks that normally ship in the workflow profile (Telegram long polling on). Redis (Valkey) stays: about 60
classes use it directly, so personal.yml runs a 128 MB one (a rate-limit counter evicted under pressure fails open, so it keeps headroom).
The lite tier (up --lite) sits between the two: Keycloak auth like the full stack, one box like
personal. lite.yml ships the Assistant's features on for it - SCHEDULED_TASKS_ENABLED,
APP_CHAT_BRANCHING_ENABLED, APP_CHAT_RESUMABLE_STREAMS_ENABLED, DATA_TABLES_ENABLED,
APP_SKILLS_LEARNING_*/APP_LEARNING_* and the BROWSER_V2/PROFILES/TAKEOVER flags (each can be set back to
false in .env) - and accepts the single-box compose profiles rag, tools, workspace and the --search
/--browser flags. APP_SIDECAR_ENABLED defaults false there (the --profile rag run sets it true) and
APP_EMBEDDINGS_PROVIDER=auto falls back to a cloud key or a registered Ollama when no sidecar runs.
Compose and launcher variables used by personal mode (none required in .env; the CLI generates the secrets):
| Variable | Default | Meaning |
|---|---|---|
PERSONAL_SPRING_PROFILES |
security,knowledge,graph-memory,personal |
profile set for core |
PERSONAL_JAVA_OPTS_EXTRA |
empty | appended to the core JVM flags (later flags win), e.g. -Xmx700m |
PERSONAL_AOT_CACHE |
false |
with --build, passes ENABLE_AOT_CACHE=true to core/Dockerfile (an AppCDS archive: faster boot, larger image) |
POSTGRES_MEM_LIMIT / CORE_MEM_LIMIT / WEB_MEM_LIMIT |
384m / 1536m / 384m |
container memory limits in personal.yml |
VEGADUTA_EDITION |
personal |
personal | lite | full. The one switch for the edition: becomes core's APP_EDITION_TIER and web's NEXT_PUBLIC_EDITION, so the two cannot disagree. The launcher writes personal into .env and never replaces a value you set. See Edition tier and features |
WEB_AUTH_MODE |
local |
becomes NEXT_PUBLIC_AUTH_MODE on web |
WEB_BILLING_ENABLED |
empty (off) | becomes NEXT_PUBLIC_BILLING_ENABLED; billing UI is hidden unless 1 |
VEGADUTA_PERSONAL_HOME |
~/.vegaduta/personal |
state for --native-core: core log, PID file, file storage (mode 700) |
VEGADUTA_CORE_JAR |
unset | jar for --native-core; else --build builds it, else it is copied out of the release's core image |
VEGADUTA_CORE_HEAP |
1g |
-Xmx of the host JVM (SerialGC) |
LOCAL_LLM_BASE_URL_NATIVE |
http://127.0.0.1:11434 |
Ollama address the host JVM uses with --local-ai |
POSTGRES_HOST_PORT / VALKEY_HOST_PORT |
5432 / 6379 |
loopback-only ports published by personal.native.yml for the host JVM |
APP_MCP_STDIO_SANDBOX |
none in personal.yml |
overrides the sandbox level of the container core |
Local sign-in (app.auth.mode=local)¶
Core is its own identity provider for one owner: RS256 tokens, rotating refresh tokens, no Keycloak. Keycloak
mode is unchanged when this is left at keycloak. Needs the security profile and APP_WEB_PUBLIC_URL.
| Property | Default | Meaning |
|---|---|---|
app.auth.mode |
keycloak |
local enables it. Blank means keycloak |
app.auth.local.data-dir |
${user.home}/.vegaduta/auth |
signing keys (signing-keys.json, file 0600, directory 0700) and the one-time first-run-setup.txt. Keep it on a persistent volume: losing the key signs everyone out. One core process per directory. personal.yml sets APP_AUTH_LOCAL_DATA_DIR=/data/storage/.auth |
app.auth.local.issuer / .audience |
https://auth.local.vegaduta.invalid / vegaduta-api |
iss and aud of the tokens; the issuer is an identifier nobody dereferences |
app.auth.local.access-token-ttl |
15m |
access token lifetime |
app.auth.local.refresh-token-ttl |
30d |
idle lifetime of one refresh token |
app.auth.local.session-max-ttl |
90d |
absolute lifetime of a login; refreshing never extends it |
app.auth.local.key-rotation-interval |
90d |
the key is rotated lazily when older than this |
app.auth.local.key-retention |
1d |
how long a retired key still verifies (floored at twice the access TTL) |
app.auth.local.setup-token-ttl |
24h |
validity of the first-run and invite links |
app.auth.local.owner-email |
owner@vegaduta.local |
sign-in e-mail of the first-run owner |
app.auth.local.owner-first-name / app.auth.local.owner-last-name |
Owner / Account |
name of that owner |
app.auth.local.tenant-name |
Personal |
name of the default tenant |
app.auth.local.owner-extra-roles |
empty | extra roles for the owner, e.g. platform-admin to reach /api/platform/** |
app.auth.local.passkey.origins / APP_AUTH_LOCAL_PASSKEY_ORIGINS |
empty (any acceptable origin) | optional comma-separated allow-list of console origins that may register or use passkeys, for example https://console.example.com,http://localhost:3000. When set, passkey options for any other origin are refused with 400 passkey_origin_unsupported. Without it core accepts https://<host>[:port] or http://localhost[:port] and refuses IP-address hosts (WebAuthn forbids an IP as relying-party ID). The relying-party ID is the origin's host and is stored with each passkey. The console's proxy forwards the browser's own origin in X-Vegaduta-Origin (core falls back to Origin). Read only when app.auth.mode=local |
Web (console) variables: NEXT_PUBLIC_AUTH_MODE (keycloak default, local to switch the sign-in page) and, server
side only, CORE_INTERNAL_URL (where the console's same-origin auth proxy reaches core; falls back to
NEXT_PUBLIC_API_BASE_URL). Setting app.auth.mode=local also sets app.identity.provider=local (override with
IDENTITY_PROVIDER). Passwords: 12 characters minimum, 72 bytes maximum. Passkeys, PINs (6 to 12 digits, bound to
a per-browser device secret; 10 wrong PINs revoke the device), recovery codes and the 10-minute freshness rule for
changing them are fixed limits, not settings; the tables are control_plane.local_passkey, local_device and
local_recovery_code (V655). The console's proxy keeps the device secret in the HttpOnly cookie vd_device
(vd_device_label holds its name; both SameSite=Strict, Path=/api/local-auth, 400 days, Secure when the
browser reached the console over HTTPS directly or through X-Forwarded-Proto). Details and limits are in
Personal mode.
Day-one defaults and starter connectors (personal)¶
Read by scripts/vegaduta personal and the personal profile; see
What is on from the start.
| Variable / property | Default | Meaning |
|---|---|---|
VEGADUTA_SEARCH |
unset (on for a fresh personal install) | 0, off, false or no in .env or the environment turns private web search off; 1, on, true or yes forces it on. A --search / --no-search flag wins for that run; this wins over the remembered answer (search= in .vegaduta-state). Read by the launcher only (scripts\vegaduta.ps1 too, where web search is off unless -Search or this asks for it) |
VEGADUTA_TOOLS |
unset (on for a fresh personal install) | the same for the MCP runner (compose profile tools). Not used with --native-core |
VEGADUTA_VALKEY |
unset (always on) | 0 leaves the Valkey container out of a personal install; rate limits, budget windows and spend counters run in core's bounded in-process fallback (they stay enforced and fail closed). --no-valkey / --valkey wins for that run; this wins over the remembered answer (valkey= in .vegaduta-state). Ignored on lite/full - shared installs keep the shared store |
app.mcp.starter.enabled / APP_MCP_STARTER_ENABLED |
true in the personal profile (the class is not loaded elsewhere) |
false registers no starter connectors even when the runner is on. They are also registered only when MCP_STDIO_SIDECAR_ENABLED=true |
MCP_STDIO_SIDECAR_ENABLED |
false; the launcher exports true with the runner |
in personal.yml this was hard-coded false; it now reads the variable, so a raw docker compose --profile tools needs it in .env |
MCP_STDIO_RUNNER_MEM_LIMIT |
768m in personal.yml (1g in the full stack) |
memory limit of the runner container |
MCP_STDIO_RUNNER_MAX_CHILDREN |
8 in personal.yml (100 in the runner itself) |
STDIO servers the runner runs at once; further spawns queue |
scripts/vegaduta personal [--no-search] [--no-tools]; --profile tools is accepted in personal mode and turns the
runner on (kept as tools=1|0 in .vegaduta-state, never in profiles=). Migration V659 (tenant) adds
mcp_starter_seed(starter_key, seeded_at), the record that makes a deleted or edited starter final.
Add-ons (/api/addons)¶
The add-on registry lists the optional capabilities of a single-box install - personal and lite
(core/providers/src/main/resources/addons/catalog.json) and records which ones a person wants; the state is
install-wide in control_plane.addon_state (V656). On a lite install the catalog's commands render as
scripts/vegaduta up --lite ... (the same profiles and flags exist there); on the full stack the console
does not advertise the page because its service set is fixed at deploy time. Core runs in a container and cannot start containers, so for
add-ons that are compose profiles it only records the wish; the host applies it. Model packs reuse
app.local-models.* and the configured Ollama (app.local-models.ollama-base-url / app.local-llm.bootstrap.base-url).
See Add-ons and the console guide.
| Property | Environment variable | Default | Meaning |
|---|---|---|---|
app.addons.host-token |
APP_ADDONS_HOST_TOKEN (Compose: ADDONS_HOST_TOKEN in .env, passed by lite.yml and docker-compose.yml; personal.yml does not yet) |
empty | shared secret the host-side installer sends as Authorization: Bearer <token> to GET /api/addons/host/wanted and POST /api/addons/host/report. At least 32 characters (openssl rand -hex 32). Empty or shorter switches the installer surface off (503 host_bridge_disabled); there is no unauthenticated fallback and loopback is not trusted. Keep it in .env, never in the repo |
app.addons.tenant-admin-may-change |
APP_ADDONS_TENANT_ADMIN_MAY_CHANGE |
true when app.auth.mode=local, otherwise false |
whether a tenant admin may switch add-ons on and off. Add-ons are install-wide (a container or a model on the machine), so on a shared install only a platform admin may unless you set this on a self-hosted box you own. Reading the list needs only a signed-in user |
Browser add-on and routine learning (personal)¶
| Property | Environment variable | Default | Meaning |
|---|---|---|---|
app.browseruse.remote-url |
APP_BROWSERUSE_REMOTE_URL |
empty | WebSocket address of a Playwright server (the browser-runner container of deploy/compose/browser.yml, ws://browser-runner:3000/). With it set the Assistant's browser is available in auto mode without Chromium in the core image; the domain policy, confirmation gates, saved logins and takeover behave as with an in-core Chromium |
app.learning.routines.min-distinct-days |
3 |
a habit must be seen on at least this many different days before it is proposed as a routine | |
app.learning.routines.retention-days |
45 |
how long routine observations are kept before the hourly prune deletes them |
Taint tracking (app.agents.taint.*)¶
| Property | Env | Default | Meaning |
|---|---|---|---|
app.agents.taint.mode |
APP_AGENTS_TAINT_MODE |
observe (enforce in personal; off before 2026-10-23, owner decision) |
off, observe (the base default: track, count and audit what would need approval, one audit row and one WARN per tool per tainted turn; behaviour unchanged, but it classifies every guarded tool call and writes audit_log_entry rows) or enforce (untrusted tool output is delimited as data and, once a turn is tainted, a tool that writes tenant data, uses stored credentials or sends data out needs approval) |
app.agents.taint.http-get-allow-hosts |
APP_AGENTS_TAINT_HTTP_GET_ALLOW_HOSTS |
empty | comma-separated hosts (and their subdomains) a tainted turn may GET through http_request / fetch_web_page; empty means no restriction on GET. Only consulted while a turn is tainted |
Metrics: agent.taint.marked{tool}, agent.taint.tainted_turns{mode}, agent.taint.sink_calls{tool,action}. Audit entries
(actor agent-taint): tool.taint.would_block, tool.taint.approval_required; they carry tool name, risk label and
sources, never arguments or output. See Security model.
STDIO MCP sandbox¶
The app.mcp.stdio properties wrap the dynamic STDIO MCP servers core spawns for tenants. The command guard and env isolation still run first.
mcp-stdio-sidecar.enabled=true takes precedence and the sandbox is then not applied a second time.
| Property | Default | Meaning |
|---|---|---|
app.mcp.stdio.sandbox |
none |
none, os (macOS sandbox-exec; Linux bubblewrap, else a degraded unshare fallback; Windows unsupported) or container (docker / podman). Env APP_MCP_STDIO_SANDBOX |
app.mcp.stdio.sandbox-required |
true |
mechanism unavailable: refuse spawns (and fail start-up, below). false runs the server unsandboxed with a WARN on each spawn |
app.mcp.stdio.preflight-fail-fast |
true |
with a required but unavailable sandbox, false lets core start and refuse every spawn instead |
app.mcp.stdio.sandbox-allow-degraded |
true |
allow the Linux unshare fallback (no filesystem confinement) |
app.mcp.stdio.sandbox-network-allow |
empty | server names (case-insensitive) or * that may use the network; empty means none, so npx -y <package> cannot download on first run |
app.mcp.stdio.sandbox-allow-loopback |
false |
with network allowed, also let the child connect to 127.0.0.1 and ::1. macOS only; off because Postgres and Valkey are published there in native mode |
app.mcp.stdio.sandbox-extra-deny-read |
empty | extra absolute paths hidden from the child: a subpath deny on macOS, a tmpfs (file: /dev/null) over the path on Linux bubblewrap. The personal profile reads VEGADUTA_MCP_DENY_READ into it (the native launcher lists the deploy/compose directory, which holds .env). Everything not listed stays readable, see Security model |
app.mcp.stdio.sandbox-extra-read-only |
empty | extra paths bound read-only in bubblewrap (e.g. an nvm prefix) |
app.mcp.stdio.sandbox-container-runtime |
auto |
auto (docker, then podman), docker or podman |
app.mcp.stdio.sandbox-container-image-node |
node:22-slim |
image for node / npx |
app.mcp.stdio.sandbox-container-image-python |
ghcr.io/astral-sh/uv:python3.12-bookworm-slim |
image for uv / uvx / python |
app.mcp.stdio.sandbox-container-memory |
512m |
--memory |
app.mcp.stdio.sandbox-container-pids |
256 |
--pids-limit (floor 16) |
app.mcp.stdio.sandbox-container-user |
auto | uid:gid; blank uses this process's on POSIX |
GET /api/system/stdio-sandbox (any authenticated user) reports {mode, mechanism, available, degraded, enforcing,
required, networkAllowAll, networkAllowCount, detail}; no paths, secrets or server names. Static STDIO connections in
application-mcp.yml are operator-configured and are not sandboxed by this setting.
The coding harness local runner is separate: runner=local needs app.coding-harness.allow-local-runner=true,
app.coding-harness.acknowledge-local-runner-risk=true (env APP_CODING_HARNESS_ACKNOWLEDGE_LOCAL_RUNNER_RISK) and
server.address set to a loopback address (unset means all interfaces and is refused). A warning is logged when it is
armed and each time a local sandbox opens.
The Assistant's terminal¶
local_shell_read and local_shell (see Personal mode). Offered only
when enabled is true, this is a one-person install and the OS sandbox above is on, available and not degraded.
| Property | Env | Default | Meaning |
|---|---|---|---|
app.assistant.local-shell.enabled |
APP_ASSISTANT_LOCAL_SHELL_ENABLED |
false (true in the personal profile) |
Offer the two tools |
app.assistant.local-shell.approval-required |
APP_ASSISTANT_LOCAL_SHELL_APPROVAL_REQUIRED |
true (false in the personal profile) |
Park every local_shell call for the person's approval. false lifts the always-confirm pin and the classification confirmation gate for local_shell only - commands run inside the sandbox, still logged; a SUPERVISED-tier agent is still gated on every call |
app.assistant.local-shell.acknowledge-shared-host |
APP_ASSISTANT_LOCAL_SHELL_ACKNOWLEDGE_SHARED_HOST |
false |
Allow it outside the personal profile; any tenant's agent then runs commands on this host |
app.assistant.api-guide.enabled |
APP_ASSISTANT_API_GUIDE_ENABLED |
true |
Offer the read-only vegaduta_api_guide tool (searches this install's OpenAPI spec) on every install. The spec is served by the API shape, so the jobs and worker shapes never offer the tool |
app.assistant.platform-guide.enabled |
APP_ASSISTANT_PLATFORM_GUIDE_ENABLED |
true |
Offer the read-only vegaduta_setup_guide tool: matches a request to the console page that sets it up, reports live status (MCP servers, channels, Google) and searches the bundled docs |
app.assistant.local-shell.workspace |
APP_ASSISTANT_LOCAL_SHELL_WORKSPACE |
~/vegaduta-workspace |
Start folder and the only writable path |
app.assistant.local-shell.default-timeout-seconds |
APP_ASSISTANT_LOCAL_SHELL_DEFAULT_TIMEOUT_SECONDS |
60 |
Time limit when the call names none |
app.assistant.local-shell.max-timeout-seconds |
APP_ASSISTANT_LOCAL_SHELL_MAX_TIMEOUT_SECONDS |
300 |
Upper cap on a call's time limit |
app.assistant.local-shell.max-output-chars |
APP_ASSISTANT_LOCAL_SHELL_MAX_OUTPUT_CHARS |
20000 |
Output returned to the model |
app.assistant.local-shell.runner-url |
APP_ASSISTANT_LOCAL_SHELL_RUNNER_URL |
empty | The workspace-runner (services/workspace-runner). When set, commands run there instead of the OS sandbox; the launcher's --profile workspace sets it to http://workspace-runner:8090 |
app.assistant.local-shell.runner-secret |
APP_ASSISTANT_LOCAL_SHELL_RUNNER_SECRET |
empty | Bearer secret the runner checks (Compose passes WORKSPACE_RUNNER_SECRET, generated by scripts/vegaduta secrets) |
app.assistant.local-shell.workspace-host-path |
APP_ASSISTANT_LOCAL_SHELL_WORKSPACE_HOST_PATH |
empty | Where the projects folder is on the host, shown in the Assistant's folder picker |
app.guardrails.max-tool-calls-per-turn |
GUARDRAILS_MAX_TOOL_CALLS_PER_TURN |
15 (150 in the personal profile) |
Tool calls one turn may make (see Tool calls: budget, rounds and result size) |
app.chat.continuation.max-passes |
APP_CHAT_CONTINUATION_MAX_PASSES |
8 |
Extra model passes a turn (any reasoning depth) may run when a reply stops at an announced-but-undone step ("Let me create the file:"); a pass that makes no tool call ends it. 0 turns it off. When a reply counts as unfinished is described under conversation-aware turns |
app.chat.finish.enabled |
APP_CHAT_FINISH_ENABLED |
true |
Group app.chat.finish. Act on the provider's finish reason (Anthropic stop_reason, OpenAI finish_reason, Gemini finishReason, Ollama done_reason): continue a reply cut off at the output limit, say so when the provider's safety system stopped a reply, retry an unusable tool call once. false ignores finish reasons, as before. An empty reply or one closed after a failure is never cached or stored as an answer whatever this says |
app.chat.finish.max-continuations |
APP_CHAT_FINISH_MAX_CONTINUATIONS |
2 |
Extra model passes allowed to finish a reply cut off at the output limit (length / max_tokens). Each is billed like any call and offers no tools. The pieces are joined; if the reply is still cut off the person gets truncated-notice and the reply is kept in the transcript as cut off (not cached). 0 never continues |
app.chat.finish.max-tool-call-retries |
APP_CHAT_FINISH_MAX_TOOL_CALL_RETRIES |
1 |
Extra passes after a tool call the provider could not use (Gemini MALFORMED_FUNCTION_CALL), tools on offer. Only when no tool has run yet in the turn (the retry cannot see this turn's tool results, so it must not repeat a side effect); otherwise the person gets malformed-tool-call-notice. 0 never retries |
app.chat.finish.continue-nudge |
(none) | the shipped prompt nudge.reply_cut_off |
Deprecated alias of the prompt nudge.reply_cut_off (sent as the next user message to make the model continue a reply cut off at the limit "exactly where it stopped"). Applied as the prompt's PLATFORM-scope default only when it differs from the shipped text, with a deprecation warning at startup; any stored override beats it. Not set in application.yml |
app.chat.finish.retry-tool-call-nudge |
(none) | the shipped prompt nudge.retry_tool_call |
Deprecated alias of the prompt nudge.retry_tool_call (sent after a malformed tool call). Same rule as continue-nudge: the platform default only, warned at startup, beaten by any stored override. Not set in application.yml |
app.chat.finish.truncated-notice |
(none) | (none; the prompt message.finish.truncated) |
Deprecated alias for the platform default of the prompt message.finish.truncated: shown after a reply that is still cut off when the continuation passes are used up. A value that differs from the shipped text is applied (and logged); a tenant or agent override of the prompt wins |
app.chat.finish.refusal-notice |
(none) | (none; the prompt message.finish.refusal) |
Deprecated alias for the platform default of the prompt message.finish.refusal: shown when the provider's safety system stopped the reply ({reason} is the provider's own finish reason). Replaces the old "could you try rephrasing" line, which blamed the wording of the request for a block it did not cause |
app.chat.finish.malformed-tool-call-notice |
(none) | (none; the prompt message.finish.malformed_tool_call) |
Deprecated alias for the platform default of the prompt message.finish.malformed_tool_call: shown when the model's tool call was unusable and the retries did not fix it: nothing was run |
app.chat.finish.empty-reply-message |
(none) | (none; the prompt message.finish.empty_reply) |
Deprecated alias for the platform default of the prompt message.finish.empty_reply: shown when the model returned nothing at all and gave no reason (was the hard-coded "I don't have a response for that - could you try rephrasing your message?"). The reply is stored as a failed reply, not as something the agent said, and is never cached |
app.chat.resilience.tool-idle-ceiling |
APP_CHAT_RESILIENCE_TOOL_IDLE_CEILING |
PT30M |
Group app.chat.resilience. How long ONE tool may run before the stream idle timer counts it as hung, measured from the tool's last start, finish or progress. While a tool is in flight this replaces app.llm.http.stream-idle-timeout. The tools have their own timeouts; this is the backstop. Only tools wrapped by the platform's tool logging report their activity: a tool added by a caller as a raw extra tool is still timed like silence |
app.chat.resilience.replay-after-journaled-effects |
APP_CHAT_RESILIENCE_REPLAY_AFTER_JOURNALED_EFFECTS |
true |
A turn that already ran tools is replayed on another provider (the BYOK fallback provider, the next rung of the hosted pool) only when every tool that reached its implementation is read-only or was recorded by the effect journal, so that an identical repeat is served from the journal instead of the provider. A tool still running, a write/send tool that ran without a journal record (journal off, or the call threw after the provider may have acted) and an unclassified tool with a mutating name all block the replay; the provider's failure is then reported as it is. The journal protects an IDENTICAL repeat only (same tool and arguments within the turn): a replayed model that sends with different arguments is not protected, which is why false exists, meaning never replay after a tool with side effects ran. Counted in chat.failover.blocked |
app.chat.resilience.retry-at-fast-when-no-tool-ran |
APP_CHAT_RESILIENCE_RETRY_AT_FAST_WHEN_NO_TOOL_RAN |
true |
A careful / rigorous / planned turn that hits its time limit BEFORE any tool ran is answered again at fast depth with its tools on offer (nothing is lost; counted in chat.degraded.depth_timeout). false reports the timeout instead. A turn that hit the limit AFTER a tool ran is always reported with the steps that ran (chat.degraded.depth_timeout_partial), kept in the transcript so the next turn sees them, and is never cached |
app.chat.resilience.list-none |
(none) | none |
Stands in for an empty list in the timeout notice |
app.chat.resilience.depth-timeout-partial-notice |
(none) | text in application.yml |
Shown when a buffered-depth turn ran out of time after tools ran. {depth}, {seconds}, {completed} (tools that ran, with repeat counts) and {running} (tools still running) are filled in. Not shown to the model |
app.chat.resilience.depth-timeout-degraded-notice |
(none) | text in application.yml |
Step-event note (guardrail event, subject depth) when a timed-out turn is answered again at fast depth; {depth} |
app.chat.resilience.replay-blocked-notice |
(none) | text in application.yml |
Step-event note (guardrail event, subject failover) when a provider failure was not retried elsewhere because tools had run; {tools} |
app.agent-turn.repair.max-attempts |
APP_AGENT_TURN_REPAIR_MAX_ATTEMPTS |
1 |
Group app.agent-turn.repair. POST /api/completions/agent-turn (hosted model for a client-side tool loop): extra model calls one turn may spend when a tool call arrives with unusable arguments (invalid or cut-off JSON). The model is handed the error as the call's result and asked again; usage of every call is summed at settlement and the reservation covers one call, so a repaired turn can cost up to 1 + max-attempts calls. 0 never asks again. If the calls are still unusable they are dropped (never returned with empty arguments) and the reply text says so, with finishReason length or malformed_tool_call |
app.agent-turn.repair.invalid-arguments-message |
(none) | text in application.yml |
Model-facing. The result handed to the model for the call whose arguments were unusable ({tool}, {reason}) |
app.agent-turn.repair.skipped-message |
(none) | text in application.yml |
Model-facing. The result handed to the model for every other call of the same reply (none of them was run) ({tool}) |
app.agent-turn.repair.invalid-reason-cut-off |
(none) | text in application.yml |
The {reason} when the arguments were cut off because the reply hit the output length limit |
app.agent-turn.repair.invalid-reason-malformed |
(none) | text in application.yml |
The {reason} when the arguments were not valid JSON for another reason |
app.agent-turn.repair.unusable-calls-notice |
(none) | text in application.yml |
Client-facing text returned when the calls are still unusable after the last attempt ({tool}, {reason}) |
The file tools workspace_list, workspace_read, workspace_search, workspace_write and workspace_edit are offered under the same
conditions as the terminal (enabled, one-person install) when the workspace folder exists in core's process. The
personal profile raises max-timeout-seconds to 1800.
Compose (--profile workspace): VEGADUTA_PROJECTS_DIR (default ~/VegaDuta-Projects), WORKSPACE_RUNNER_SECRET,
WORKSPACE_RUNNER_MAX_TIMEOUT_SECONDS (1800), WORKSPACE_RUNNER_MEM_LIMIT (3g),
WORKSPACE_RUNNER_PIDS_LIMIT (2048: the most processes and threads the container may have at once, so a runaway command is stopped by the
container and not by the host; a Maven, Gradle or Next.js build peaks below 200; raise it only for a very large parallel build).
Network from the terminal follows the sandbox's per-server list: add local-shell (or *) to
app.mcp.stdio.sandbox-network-allow.
Limits that were constants (package 3c-2)¶
These were literals in the Java code; each is a setting now with the same default, so nothing changes until an operator sets one. The defaults are also in a .properties file in the owning module (the fallback when application.yml is not loaded); the two are kept equal.
Agent max-tokens ceiling. app.agents.max-tokens-ceiling is the one definition of "the most output tokens an agent's reply may be given". It bounds POST /api/agents and PUT /api/agents/{id}/generation-params (a larger maxTokens is a 400 whose message names the configured limit), the Simple-mode tuning that infers a value from the system prompt, and what one hosted-pool turn reserves of the tenant's monthly token budget (a stored value above the ceiling is reserved as the ceiling, so lowering it only ever tightens). It used to be the literal 8192 in AgentCreateRequest, AgentGenerationParamsUpdateRequest, GenerationParamsInferenceService and FreeTierUsageLimiterService. The web screens (GenerationTuningSection.tsx, blueprintFields.ts) still slide up to 8192: no existing API the console loads carries the ceiling, so raising it needs a new endpoint to follow. Lowering it is safe at once, because the server rejects the larger value and says why. Other copies of 8192 are not covered by this setting: core/brief (BriefValidator, EnvoyAgentPorts) and ResearchRunPlan.AGENT_MAX_TOKENS.
| Key | Env var | Default | Effect |
|---|---|---|---|
app.agents.max-tokens-ceiling |
APP_AGENTS_MAX_TOKENS_CEILING |
8192 |
most output tokens an agent may be given (create, update, Simple-mode tuning, hosted-pool reservation) |
app.agents.inferred-max-tokens-floor |
APP_AGENTS_INFERRED_MAX_TOKENS_FLOOR |
128 |
least value the Simple-mode tuning proposes (was MIN_MAX_TOKENS) |
app.agents.collaboration.max-branches |
APP_AGENTS_COLLABORATION_MAX_BRANCHES |
8 |
branches one delegate_parallel or run_subtasks call may start; the rest come back as "over the limit" entries, and the swarm verifier re-dispatches at most this many per repair round. Was MAX_BRANCHES in DelegateToTeamToolCallback and RunSubtasksToolCallback. This is the platform value of the setting delegation.maxBranches: a workspace or an agent can override it in the console (docs/settings.md) |
app.agents.collaboration.branch-timeout-seconds |
APP_AGENTS_COLLABORATION_BRANCH_TIMEOUT_SECONDS |
240 |
wall-clock limit of one branch (BRANCH_TIMEOUT_SECONDS, duplicated in both tools). The platform value of the setting delegation.branchTimeoutSeconds (workspace or agent override, 10-3600) |
app.agents.collaboration.max-answer-chars |
APP_AGENTS_COLLABORATION_MAX_ANSWER_CHARS |
4000 |
characters of one branch's answer handed back to the calling model; a longer one is cut and ends with "…" (MAX_ANSWER_CHARS, duplicated in both tools) |
app.agents.collaboration.max-role-chars |
APP_AGENTS_COLLABORATION_MAX_ROLE_CHARS |
80 |
characters kept of a run_subtasks worker's role label (MAX_ROLE_CHARS) |
app.agent-turn.limits.max-messages |
APP_AGENT_TURN_LIMITS_MAX_MESSAGES |
400 |
messages in one POST /api/completions/agent-turn conversation; more is a 400 (AgentTurnController.MAX_MESSAGES) |
app.agent-turn.limits.max-total-chars |
APP_AGENT_TURN_LIMITS_MAX_TOTAL_CHARS |
600000 |
characters of message content in that conversation; more is a 413 (MAX_TOTAL_CHARS) |
app.agent-turn.limits.max-tools |
APP_AGENT_TURN_LIMITS_MAX_TOOLS |
40 |
tool definitions the client may offer; more is a 400 (MAX_TOOLS) |
app.agent-turn.limits.max-tool-calls-per-message |
APP_AGENT_TURN_LIMITS_MAX_TOOL_CALLS_PER_MESSAGE |
32 |
tool calls on one assistant message; more is a 400 (MAX_TOOL_CALLS_PER_MESSAGE) |
app.llm.request-snapshot.max-message-chars |
APP_LLM_REQUEST_SNAPSHOT_MAX_MESSAGE_CHARS |
40000 |
LLM request inspector: characters of one message kept (display only; was LlmRequestSnapshotter.MAX_MESSAGE_CHARS) |
app.llm.request-snapshot.max-total-chars |
APP_LLM_REQUEST_SNAPSHOT_MAX_TOTAL_CHARS |
300000 |
characters of all messages kept (MAX_TOTAL_CHARS) |
app.llm.request-snapshot.max-schema-chars |
APP_LLM_REQUEST_SNAPSHOT_MAX_SCHEMA_CHARS |
4000 |
characters of one tool's input schema kept (MAX_SCHEMA_CHARS) |
app.llm.request-snapshot.max-tools |
APP_LLM_REQUEST_SNAPSHOT_MAX_TOOLS |
200 |
tool definitions kept (MAX_TOOLS) |
app.llm.request-snapshot.max-call-chars |
APP_LLM_REQUEST_SNAPSHOT_MAX_CALL_CHARS |
600 |
characters of one tool call's arguments or one tool result kept (the literal 600) |
app.llm.request-snapshot.truncation-marker |
(none) | \n… [truncated for display] |
appended to a message that was cut |
app.llm.request-snapshot-store.mode |
APP_LLM_REQUEST_SNAPSHOT_STORE_MODE |
redis |
where the request inspector keeps its redacted copies of recent model requests: redis also writes each copy to Redis (the one the rate limiter uses), so any replica can answer "what was sent" for a turn another replica ran and the copies survive a restart for the ttl; memory is this JVM only (the behaviour before 2026-10). With redis and no Redis connection the store logs it and keeps memory only. Copies hold user text (redacted and capped), so they expire after the ttl in both places and are never written to a database |
app.llm.request-snapshot-store.ttl |
APP_LLM_REQUEST_SNAPSHOT_STORE_TTL |
6h |
how long a copy is kept, in memory and in Redis (was LlmRequestSnapshotStore.TTL) |
app.llm.request-snapshot-store.max-sessions |
APP_LLM_REQUEST_SNAPSHOT_STORE_MAX_SESSIONS |
300 |
sessions kept in memory; the least recently used leave first (was MAX_SESSIONS) |
app.llm.request-snapshot-store.max-per-session |
APP_LLM_REQUEST_SNAPSHOT_STORE_MAX_PER_SESSION |
80 |
model calls kept per session, in memory and in Redis (was MAX_PER_SESSION) |
app.llm.request-snapshot-store.write-queue-size |
APP_LLM_REQUEST_SNAPSHOT_STORE_WRITE_QUEUE_SIZE |
256 |
copies waiting to be written to Redis; when full the oldest waiting copy is skipped (it stays in memory on the replica that took it), so a slow Redis never slows a model call |
app.llm.request-snapshot-store.key-prefix |
APP_LLM_REQUEST_SNAPSHOT_STORE_KEY_PREFIX |
agentic:llm-request-snapshots: |
Redis key prefix; the key is the prefix, the tenant, \| and the session id |
app.tools.mcp.request-timeout-seconds |
APP_TOOLS_MCP_REQUEST_TIMEOUT_SECONDS |
60 |
seconds one request to a tenant's registered MCP server (tool call, tools/list, initialize, health check) may wait (DynamicMcpConnectionManager.REQUEST_TIMEOUT). Applies to connections opened after the change. Separate from app.agent-runtime's per-call tool timeout, which stops a call from outside |
app.tools.mcp.test-timeout-seconds |
APP_TOOLS_MCP_TEST_TIMEOUT_SECONDS |
20 |
the same for the "test connection" action (McpConnectionTester.TEST_TIMEOUT) |
app.tools.openapi.connect-timeout-seconds |
APP_TOOLS_OPENAPI_CONNECT_TIMEOUT_SECONDS |
10 |
seconds an OpenAPI tool call, token call or spec fetch may take to connect (OpenApiToolExecutor.CONNECT_TIMEOUT) |
app.tools.openapi.request-timeout-seconds |
APP_TOOLS_OPENAPI_REQUEST_TIMEOUT_SECONDS |
30 |
seconds such a call may take to get its reply (REQUEST_TIMEOUT) |
app.tools.openapi.max-response-bytes |
APP_TOOLS_OPENAPI_MAX_RESPONSE_BYTES |
1048576 |
bytes of an API reply that are read (MAX_RESPONSE_BYTES) |
app.tools.openapi.max-result-chars |
APP_TOOLS_OPENAPI_MAX_RESULT_CHARS |
16000 |
characters of an API reply handed to the model; a longer one is clipped and the result says so (MAX_RESULT_CHARS) |
app.tools.openapi.max-spec-redirects |
APP_TOOLS_OPENAPI_MAX_SPEC_REDIRECTS |
3 |
redirects followed when fetching a spec; each hop is re-checked against the SSRF guard (MAX_SPEC_REDIRECTS) |
The groups above are app.agents (max-tokens-ceiling, inferred-max-tokens-floor), app.agents.collaboration, app.agent-turn.limits, app.llm.request-snapshot, app.llm.request-snapshot-store, app.tools.mcp and app.tools.openapi.
The tool-call ceiling per turn and its autonomy-tier scaling (supervised divisor and floor, autonomous multiplier) are app.guardrails.max-tool-calls-per-turn and app.guardrails.step-limit.*, and the knowledge-search top-k and minimum score are app.agent-runtime.defaults.retrieval-top-k / retrieval-min-score (and the agent and tenant overrides of them); both were made settings by earlier packages and are unchanged here.
The Assistant's desktop tools¶
local_desktop_* — computer-use on the person's own screen through the desktop-agent sidecar
(services/desktop-agent), which runs ON the laptop (never a container): screenshots, the
accessibility element tree, click/type/key, open, notifications and clipboard. Offered only on a
one-person install while agent-url is set — the URL being configured IS the opt-in.
| Property | Env | Default | Meaning |
|---|---|---|---|
app.assistant.desktop.agent-url |
APP_ASSISTANT_DESKTOP_AGENT_URL |
empty | The sidecar (http://127.0.0.1:8787, or http://host.docker.internal:8787 when core runs in Docker) |
app.assistant.desktop.agent-secret |
APP_ASSISTANT_DESKTOP_AGENT_SECRET |
empty | Bearer secret; must match the sidecar's DESKTOP_AGENT_SECRET |
app.assistant.desktop.approval-required |
APP_ASSISTANT_DESKTOP_APPROVAL_REQUIRED |
true (false in the personal profile) |
Pin local_desktop_press/click/type/key/open/activate for approval — these actions are NOT sandboxed. false lets them run like ordinary tools; a SUPERVISED agent still asks |
Scheduled tasks (app.scheduled-tasks.enabled, SCHEDULED_TASKS_ENABLED) are true in the
personal profile — the recurring "check X every morning and ping me" loop; delivery
whatsapp_qr sends through the linked WhatsApp QR channel. See
Personal mode.
SCHEDULED_TASKS_DELIVERY_CHANNELS picks which destinations the form and API accept
(default enables all): in_app, email, whatsapp, whatsapp_qr, telegram, slack,
signal, matrix, imessage, a2a, voice. The channel sends go through the normal
approval policy; a2a delivers to a remote agent's card URL through the outbound A2A client
(needs APP_A2A_CLIENT_ENABLED=true and the host in APP_A2A_CLIENT_ALLOWED_HOSTS), and
voice places an outbound call that speaks the result (needs the tenant's Twilio voice
credential, trimmed to ~900 spoken characters).
Skills¶
| Property | Default | Meaning |
|---|---|---|
app.skills.learning.enabled |
false |
the learning loop: after a completed multi-step turn that used no skill, draft one and park it as pending for a person to approve. true in personal |
app.learning.proposals.enabled |
false |
the learning-proposal store behind every "the assistant learns" loop: lessons, preferences and the like wait here as PENDING until the person approves them (GET /api/learning/proposals, POST .../{id}/approve, .../reject); nothing reaches a prompt, a schedule or a stored preference before approval. true in personal; off, the routes 404 |
app.learning.lessons.enabled |
false |
the lessons loop: a tool call that failed and was retried with different arguments, or a user message that corrects the previous answer, becomes one short imperative lesson (one small model call on the agent's own model, off the request thread, scrubbed, per-agent cooldown and daily cap from app.learning.proposals.*) filed as a PENDING lesson proposal. Needs app.learning.proposals.enabled. true in personal |
app.learning.proposals.max-pending |
50 |
pending proposals one person can have; further ones are dropped until some are decided |
app.learning.proposals.ttl-days |
30 |
pending proposals older than this expire |
app.learning.proposals.cooldown-minutes |
30 |
minimum time between two learning runs for the same agent and loop |
app.learning.proposals.max-per-agent-per-day |
6 |
learning runs per agent and loop in any 24 hours |
app.learning.preferences.enabled |
false |
the preferences-from-behavior loop (needs app.learning.proposals.enabled): counts repeated style requests in chat messages and thumbs comments ("shorter", "no emojis", "reply in Hindi") and the person's own approve/reject habits, and files a PREFERENCE proposal once a signal repeats. No model call. Approval habits are informational only and never change an approval rule. true in personal |
app.learning.preferences.threshold |
3 |
how many times a signal must repeat within the window before it is proposed (minimum 2) |
app.learning.preferences.window-days |
30 |
the counting window, in days (1 to 365) |
app.learning.routines.enabled |
false |
routines from habits: requests you repeat on a rhythm (same intent on at least 3 distinct days, at about the same time; daily, every weekday or weekly) become a ROUTINE proposal that, once approved, creates a scheduled task. Needs app.learning.proposals.enabled and app.scheduled-tasks.enabled. Your requests are kept scrubbed (one that needed scrubbing is never kept) for retention-days, in routine_observations (V633); no model call. true in personal |
app.learning.routines.timezone |
server zone | the time zone "around 9am" is read in and the proposed task runs in (e.g. Asia/Kolkata); env APP_LEARNING_ROUTINES_TIMEZONE |
app.learning.routines.similarity |
0.6 |
how alike two requests must be (shared content words, 0.3-0.95) to count as the same intent |
app.learning.routines.min-occurrences / min-distinct-days |
3 / 3 |
evidence needed before proposing (neither goes below 3) |
app.learning.routines.max-time-spread-minutes |
120 |
the first request of each day must fall within this window to count as "the same time" |
app.learning.routines.window-days / retention-days |
28 / 45 |
how far back requests are compared / how long they are kept |
app.learning.review.enabled |
false |
the daily memory review (needs app.learning.proposals.enabled): once per person per day, off the chat path and with no model call, it compares your remembered facts and files CONSOLIDATION proposals for near-duplicates (keep the more informative), contradictions (keep the newer) and facts unused for stale-days (forget). Approving performs the merge or forget through the Memory services; nothing changes before. Also serves GET /api/learning/review/summary (what was learned this week). true in personal |
app.learning.review.stale-days |
60 |
a fact not saved or recalled for this many days is offered for forgetting (minimum 7) |
app.learning.review.max-per-run |
5 |
most proposals one review of one person files |
app.learning.review.max-people-per-tick |
200 |
most people one hourly tick reviews in a tenant |
app.skills.learning.self-check.enabled |
false |
before a drafted skill is queued, one extra model call (the agent's own model, off the request thread, inside the same per-agent cooldown and daily cap) judges whether following it would have answered the originating request at least as well. The verdict (PASS, WEAK) is stored with the pending skill (V653) and shown as a "checked" badge in the Learned section and the skill library; a FAIL draft is dropped and logged; no verdict (call failed, limit hit) queues it unchecked. true in personal |
app.skills.learning.min-tool-calls |
3 |
tool calls a turn needs before it is considered (minimum 2) |
app.skills.learning.cooldown-minutes |
30 |
per agent, between drafting attempts |
app.skills.learning.max-drafts-per-agent-per-day |
3 |
per agent drafting attempts (model calls, not stored drafts) |
app.skills.learning.max-transcript-chars |
6000 |
transcript sent to the model (1,000 to 20,000) |
app.skills.learning.stale-days |
90 |
a skill unused this long is flagged stale; used by the lifecycle view even with the loop off |
At most 10 drafts wait for review per workspace. See Personal mode.
Edition tier and features¶
core runs as one edition, app.edition.tier (APP_EDITION_TIER): personal, lite or full. The personal profile sets personal; with no tier set, or lite or full, every feature below is on, so Lite, Full and the worker and jobs shapes are unchanged. In Compose you do not set the tier itself: VEGADUTA_EDITION in deploy/compose/.env feeds both core's APP_EDITION_TIER and the console's NEXT_PUBLIC_EDITION (deploy/compose/personal.yml).
A feature that is off is not registered at all: its controllers, services, schedulers and agent tools do not exist on that core, and its endpoints answer 404. Tables, entities, repositories and migrations are not affected, so turning a feature on later finds its data intact. Each feature is decided once, at boot.
| Feature id | Personal | What it covers |
|---|---|---|
billing-payments |
off | payment gateways (Razorpay, Stripe, PhonePe), checkout, invoices, subscriptions and upgrades, budget top-ups, the agent wallet, payment mandates, the commerce feed (/api/commerce/feed) and the create_payment_link, request_upi_collect and check_payment_status agent tools. The x402 seller endpoints answer "payments disabled" |
social-channels |
off | the Slack, Discord, Microsoft Teams and X channel adapters, webhooks and credential endpoints |
a2a-directory |
off | the A2A JSON-RPC and REST endpoints, agent cards and the public agent directory |
mcp-server-exposure |
off | /api/dev/v1 and /api/mcpserver/exposure. The grants endpoint stays registered while app.openai-compat.enabled=true |
operator-console |
off | the platform operator endpoints for tenants, users, plans, spend, abuse, moderation, fleet, impersonation, audit and ops, plus the demo, newsletter and landing surfaces. The platform LLM pool, tool credentials and platform settings are not part of it |
cloud-speech |
off | the Sarvam and ElevenLabs integrations (speech, voice cloning, music). An add-on: switch it on, restart core, add the key in the console |
voice-realtime-share |
off | anonymous realtime voice on share links, call recording and the LiveKit egress |
video-demo-media |
off | Vertex Veo video generation and the demo image and video endpoints |
How each feature is decided, first match wins:
app.edition.features.<feature id>.enabled(environment:APP_EDITION_FEATURES_<ID>_ENABLED, the id with-written as_, for exampleAPP_EDITION_FEATURES_CLOUD_SPEECH_ENABLED=true). It is read as a boolean, sotrue/false,1/0,on/offandyes/nowork; a blank value counts as unset. Put it indeploy/compose/core.envand restart core.billing-paymentsonly:app.edition.billing-enabled(APP_EDITION_BILLING_ENABLED, defaulttrue, see below).- Otherwise the tier:
personalis off, anything else (or no tier) is on.
Rules of the road: a gated bean is only injected by beans gated by the same feature, or through ObjectProvider, Optional or a collection (EditionClosureRulesTest checks it from bytecode); a new gated bean needs nothing else, because EditionWiringTest finds them by annotation.
GET /api/public/status reports the result in an edition object: {tier, billingEnabled, authMode, features}, where features maps each id above to true or false. It is computed by the same code as the bean conditions. The console takes its tier from NEXT_PUBLIC_EDITION and, once the status arrives, warns and raises the platform status banner if it differs from core's.
Billing edition¶
| Property | Env | Default | Meaning |
|---|---|---|---|
app.edition.billing-enabled |
APP_EDITION_BILLING_ENABLED |
true |
false: no plans, prices, payments, billing mail or upgrade prompts; the billing, checkout, invoice and top-up endpoints are not registered; every limit resolves as unlimited. Usage is still counted. This is the billing-payments edition feature: an explicit app.edition.features.billing-payments.enabled wins over it. The console follows NEXT_PUBLIC_BILLING_ENABLED separately |
Local models¶
| Property | Env | Default | Meaning |
|---|---|---|---|
app.local-models.ollama-base-url |
(none) | app.local-llm.bootstrap.base-url, then http://ollama:11434 |
the only Ollama the local-model API talks to; no request parameter can change it. A loopback or Compose-DNS address must also be in APP_SECURITY_OUTBOUND_ALLOWED_PRIVATE_HOSTS |
app.local-models.host-ram-gb |
HOST_RAM_GB |
0 |
the host's memory in GB, set by scripts/vegaduta personal (and scripts\vegaduta.ps1 personal on Windows, from the computer, not the WSL2 VM) for a containerised core (which would otherwise report its own cgroup limit). 0 measures what core sees |
app.local-models.host-accelerator |
HOST_ACCELERATOR |
blank | apple, or nvidia:NAME:VRAM_GB (from nvidia-smi, also on Windows): what the host has, for the same reason. Blank detects from inside core |
app.local-models.allow-uncataloged-pull |
(none) | false |
allow pulling tags that are not in the catalog |
app.local-models.keep-alive |
APP_LOCAL_MODELS_KEEP_ALIVE |
30m; the Compose local-ai overlay sets it and Ollama's own OLLAMA_KEEP_ALIVE from OLLAMA_KEEP_ALIVE (default -1) |
Ollama keep_alive sent by POST /api/local-models/warm (and by the warm-ups core runs itself after a pull, a /use switch and the local-LLM bootstrap): how long the loaded model stays in memory. Ollama's own default is five minutes, after which the next chat pays the whole weight load again; -1 keeps it forever. A chat request sent without the field resets the timer to the server's default, so for a host Ollama also set OLLAMA_KEEP_ALIVE on the server (the Compose local-ai overlay already does) |
app.llm.ollama.reasoning-effort |
APP_LLM_OLLAMA_REASONING_EFFORT |
none for the install's own local model (the platform-pool row Providers -> Local model manages, which can override it per row); blank for agents on an OLLAMA provider. The Compose local-ai overlay sets none (LOCAL_LLM_REASONING_EFFORT) |
reasoning_effort sent to Ollama's OpenAI-compat endpoint: none skips the hidden thinking pass of reasoning models (qwen3.5, qwen3, deepseek-r1, gpt-oss), which on a laptop is most of the wait (a one-line question took 40+ s on qwen3.5:4b against about 5 s with it off); low/medium/high leave it on at that effort; blank sends nothing (the model's default). An unsupported name falls back to the model's default, so it cannot fail a call |
app.local-models.rate-limit.reads-per-minute |
(none) | 120 |
per tenant and caller |
app.local-models.rate-limit.probes-per-minute |
(none) | 6 |
|
app.local-models.rate-limit.pulls-per-minute |
(none) | 6 |
|
app.llm.ollama.num-ctx |
APP_LLM_OLLAMA_NUM_CTX |
32768 |
num_ctx sent to Ollama per request (its OpenAI-compat options field) - the provider default (~4k) silently truncates tool-heavy prompts |
app.llm.ollama.num-batch |
APP_LLM_OLLAMA_NUM_BATCH |
0 (server default 512) |
num_batch sent to Ollama per request - tokens evaluated per prompt step; a larger value (e.g. 2048, keep <= num-ctx) speeds up the multi-thousand-token fixed-prefix eval that dominates turn latency on a local model |
app.llm.structured-output.ollama-format.enabled |
(none) | false |
when on, a structured-output request that Ollama refuses (the MLX runner's json_schema 400) is retried with the schema in the prompt |
A LOCAL_OPENAI provider (vLLM, llama.cpp, LM Studio, Jan, LocalAI) adds no keys. Its server must be listed in
app.security.outbound.allowed-private-hosts (for example localhost:8000, or host.docker.internal:1234 when core
runs in Docker), and app.llm.tool-call-probe.enabled (default true) governs its tool-calling check.
Channels and inbound limits¶
| Property | Env | Default | Meaning |
|---|---|---|---|
app.signal.receive.enabled |
(none) | true |
the receiver is idle until a number is connected; never runs in the jobs or worker shapes |
app.signal.receive.mode |
(none) | auto |
poll or websocket force one |
app.signal.receive.poll-seconds |
(none) | 15 |
long-poll length, 1 to 25 |
app.signal.max-message-chars / app.signal.max-attachment-bytes |
(none) | 3500 / 20971520 |
|
app.whatsappqr.bridge-url / app.whatsappqr.bridge-secret |
APP_WHATSAPPQR_BRIDGE_URL / APP_WHATSAPPQR_BRIDGE_SECRET |
empty / empty | the whatsapp-qr bridge address and shared token; both empty hides the channel - see WhatsApp QR |
app.whatsappqr.receive.enabled |
APP_WHATSAPPQR_RECEIVE_ENABLED |
true |
the receiver is idle until a session is linked; one process per bridge |
app.whatsappqr.receive.poll-seconds |
(none) | 15 |
long-poll length, 1 to 20 |
app.whatsappqr.max-message-chars |
(none) | 60000 |
|
app.matrix.sync.enabled |
APP_MATRIX_SYNC_ENABLED |
false |
run the /sync loop in this process; one process per bot. The personal profile does not set it |
app.imessage.polling.enabled |
(none) | false |
start the BlueBubbles polling fallback |
app.imessage.polling.interval-ms |
(none) | 5000 |
poll period (minimum 1000) |
app.imessage.rate-limit.inbound-per-minute |
(none) | 20 |
per chat; 0 disables |
app.imessage.rate-limit.outbound-per-minute |
(none) | 30 |
per tenant; 0 disables |
app.omnichannel.rate-limit.enabled |
APP_OMNICHANNEL_RATE_LIMIT_ENABLED |
false (true in personal) |
opt-in cap on inbound turns for WhatsApp, Telegram, Slack, Discord, Teams, Meta social, X, email and SMS (voice is exempt) |
app.omnichannel.rate-limit.per-sender-per-minute |
(none) | 20 |
per tenant, channel and sender; 0 disables the counter. On Slack, Discord, Teams and X the "sender" is the channel or conversation |
app.omnichannel.rate-limit.per-channel-per-minute |
(none) | 600 |
per tenant and channel; 0 disables |
An over-limit message is dropped without a reply on the channel, costs no session write or model call, and the API
answers 429 with Retry-After. Signal, Matrix, iMessage, the WhatsApp QR bridge and an email (IMAP) mailbox reach private hosts only through
app.security.outbound.allowed-private-hosts (for example signal-cli:8080, whatsapp-qr:8080, the Mac's host:port, a LAN
homeserver, a mail server on your own network). The IMAP host and port a tenant enters are checked before any connection
is opened and again on every poll; a stored mailbox that is no longer allowed is skipped with a WARN naming it.
Voice sockets and guardrails¶
| Property | Env | Default | Meaning |
|---|---|---|---|
app.voice.realtime.allowed-origins |
(none) | ${app.web.allowed-origins} plus same-origin |
origins that may open the browser realtime-voice socket; * allows any |
app.voice.relay.allowed-origins |
(none) | empty | origins on the Twilio ConversationRelay socket; empty is a same-origin check only, which admits Twilio (it sends no Origin) and refuses browser pages |
app.guardrails.fail-mode |
APP_GUARDRAILS_FAIL_MODE |
closed |
closed: a guardrail check that itself fails refuses the call or withholds the output. open restores the old fail-open behaviour and logs a warning. Behaviour change: a per-agent tool-argument policy lookup that throws (app.tool-arg-policies) used to let the call through in every mode; it now follows this setting, so with the default a failing policy store refuses the agent's tool calls with a "could not be checked" result instead of silently dropping the policies |
app.guardrails.judge.max-input-chars |
APP_GUARDRAILS_JUDGE_MAX_INPUT_CHARS |
4000 |
Group app.guardrails.judge. Characters of the checked text (a tool result, a user message, tool-call arguments) put in front of an LLM judge: the tool-output injection judge, the toxicity judge, the RIGOROUS tool-call verifier and the moderation policy-pack classifier. The rest is not judged. Was a constant in each of the four classes |
app.guardrails.judge.truncation-marker |
(none) | …[truncated] |
Appended to judge input cut at max-input-chars (model-facing; the leading space is part of the value) |
app.guardrails.judge.temperature |
APP_GUARDRAILS_JUDGE_TEMPERATURE |
empty (inherit) | Sampling temperature of judge calls. Empty keeps the calling agent's own setting, which is what the judges used before this existed (an agent at 0.9 gets a noisy verdict); 0.0 pins judges. Sent as per-call options |
app.guardrails.judge.max-tokens |
APP_GUARDRAILS_JUDGE_MAX_TOKENS |
empty (inherit) | Output-token cap of judge calls. Empty keeps the agent's own cap (an agent with a very small cap can truncate a verdict); the verdicts are one line |
app.guardrails.judge.model |
APP_GUARDRAILS_JUDGE_MODEL |
empty (agent's model) | Model id for judge calls, a per-call override on the same provider and credentials as the agent's chat client. It does not switch provider. Not yet applied to the answer-consistency and Reflexion judges, which live in core/agents-turn |
app.guardrails.jailbreak.max-scanned-chars |
APP_GUARDRAILS_JAILBREAK_MAX_SCANNED_CHARS |
16000 |
Characters of a message or reply the jailbreak patterns scan. Text beyond it is ignored, not rejected, so a payload padded past the window is not seen; raise it to close that (the patterns are bounded, the cost is linear). The chat gateway's prompt-injection screener has its own window |
app.guardrails.toxicity.block-score |
APP_GUARDRAILS_TOXICITY_BLOCK_SCORE |
60 |
The toxicity judge's 0-100 score at or above which content is blocked. Lower blocks more |
app.guardrails.tool-verifier.fail-mode |
APP_GUARDRAILS_TOOL_VERIFIER_FAIL_MODE |
inherit |
What the RIGOROUS tool-call verifier does when its model call errors or answers nothing. inherit: follow app.guardrails.fail-mode (closed unless changed), so the verifier agrees with the injection judge and the dispatch gate; open: approve the call; closed: refuse it. Behaviour change: the verifier used to fail open always (including on a blank answer); with the default it now refuses the call. A missing chat client is "not configured" and approves in every mode |
app.guardrails.tool-verifier.unavailable-reason |
(none) | the tool-call check could not be completed - failed closed |
Model-facing. The reason the verifier gives for refusing a call it could not check (fail-closed only) |
app.guardrails.tool-verifier.context-messages |
APP_GUARDRAILS_TOOL_VERIFIER_CONTEXT_MESSAGES |
6 |
How many of the latest messages of the conversation (user and assistant, oldest first, within app.guardrails.judge.max-input-chars) the RIGOROUS verifier is shown in front of the current user message, read from the turn's session memory under its scoped id. A follow-up such as "yes, send it" used to be judged against "yes". 0 = the current message alone (the old behaviour). The framing line is the prompt key judge.tool_verifier.context |
app.guardrails.supervised.gate-read-only |
APP_GUARDRAILS_SUPERVISED_GATE_READ_ONLY |
true (false in the personal profile) |
Group app.guardrails.supervised. true: the SUPERVISED autonomy tier asks a human before every tool call, a search or a lookup included (the behaviour before this existed). false: a call runs without approval only when ALL hold: the tool has an explicit classification row that says read-only (a stored row or a compiled-in declaration; never an unclassified tool, a quarantined one, or an MCP server's own read-only hint), the agent is positively judged not to hold the lethal trifecta (app.tools.classification.trifecta-*; an agent that does, or that cannot be judged, keeps full gating), and nothing else asks for approval (taint, a procedure, a hook, the confirmation gate). Argument policies, hooks, budget and step limits, the circuit breaker and every other guard still run on the call. Each such call is written to the audit log (tool.supervised.auto_allowed) and to the turn journal as auto-allowed: read-only under SUPERVISED, and counted in agent.tool.supervised.auto_allowed. A security setting (guardrails.supervised.readOnlyCalls in the settings catalog): a workspace administrator or an agent can only choose gate, never loosen a platform true. An Auto-select agent (empty tool allow-list) is judged by the trifecta analysis only when app.tools.classification.trifecta-analyse-auto-select is true |
app.guardrails.approval-park.awaiting-message |
(none) | text in application.yml |
Group app.guardrails.approval-park (model-facing instructions, editable). What the model reads when its tool call is held for human approval: the action is awaiting approval and was not run, the task resumes automatically once it is decided, tell the user briefly and end the turn without abandoning the goal. {tool} and {requestId} are filled in Compatibility alias of the prompt toolresult.park.awaiting (prompts): an override of the key at any scope wins, then this value when it differs from the shipped wording, then the shipped wording. |
app.guardrails.approval-park.queued-message |
(none) | text in application.yml |
What the model reads for a tool call it makes after the turn parked: the call was not run, was recorded as its own pending approval and runs once approved. {tool}, {requestId}, {parkedTool} Alias of toolresult.park.queued, same order. |
app.guardrails.approval-park.queue-full-message |
(none) | text in application.yml |
The same, when max-queued-calls calls are already queued: the call was neither run nor queued Alias of toolresult.park.queue_full, same order. |
app.guardrails.approval-park.record-failed-message |
(none) | text in application.yml |
What the model reads when the approval request itself could not be recorded (database error): nothing is waiting, the call was not run, the turn is not parked. {tool} Alias of toolresult.park.record_failed, same order. |
app.guardrails.approval-park.max-queued-calls |
APP_GUARDRAILS_APPROVAL_PARK_MAX_QUEUED_CALLS |
5 |
Calls made after a turn parked on an approval that are recorded as their own pending approvals (so they run once approved instead of being lost). Further calls are refused unrecorded; 0 queues none |
app.guardrails.approval-park.max-goal-chars |
APP_GUARDRAILS_APPROVAL_PARK_MAX_GOAL_CHARS |
4000 |
How much of the user message that started a turn parked on an approval is stored in the approval's replay envelope, so the task can be continued with its goal restated after the decision. Longer messages are cut with a marker. 0 stores no goal: the request stays replayable but the platform does not continue the task |
app.guardrails.approval-park.goal-cut-marker |
(none) | [...message shortened] |
Appended to a recorded goal cut at max-goal-chars (model-facing; the leading space is part of the value), so the agent knows the message it is shown was shortened |
app.approvals.expiry.ttl |
APP_APPROVALS_EXPIRY_TTL |
0 (off) |
How long an approval request may stay pending, from its creation. 0 (default): never expires, as before. When set, a sweep closes older pending requests: stored as REJECTED by system:approval-expiry with expired-reason as the reason (no new status value, so the API, SDK and UI contracts are unchanged), audited as EXPIRED, announced as approval.resolved with decision expired. A later approve is refused as already decided, so a stale action can no longer be approved into running; an executor with rejection cleanup (a halted workflow node) is told; a parked agent job fails as APPROVAL_TIMEOUT (or, with jobs-continue-after-denial, resumes with the expiry as the result). Keep it at least as long as app.agent-jobs.approval.park-timeout if the job should time out first |
app.approvals.expiry.sweep-interval |
APP_APPROVALS_EXPIRY_SWEEP_INTERVAL |
PT5M |
How often the expiry sweep runs (one replica at a time, every tenant schema). Does nothing while ttl is 0 |
app.approvals.expiry.initial-delay |
APP_APPROVALS_EXPIRY_INITIAL_DELAY |
PT1M |
Delay after startup before the first sweep |
app.approvals.expiry.batch-size |
APP_APPROVALS_EXPIRY_BATCH_SIZE |
200 |
Most requests expired per tenant per sweep; the rest wait for the next tick |
app.approvals.expiry.expired-reason |
(none) | text in application.yml |
Model-facing. The rejection reason recorded on an expired request and read back by the agent that asked: the action was not performed, the request can no longer be approved, ask again or take another route. {ttl} is the wait, for example 24 hours |
app.approvals.continuation.enabled |
APP_APPROVALS_CONTINUATION_ENABLED |
true |
After a human approves, fails or denies a gated tool call the platform continues the original task by itself: for a chat session it starts a turn (same agent, session and person) that restates the user's goal and hands the agent the tool's result; an agent job's resumed turn is built from the same message. false: a decision only runs or refuses the tool and writes the history note |
app.approvals.continuation.max-result-chars |
APP_APPROVALS_CONTINUATION_MAX_RESULT_CHARS |
16000 |
Longest tool output (or failure text, or denial reason) put into the continuation message; longer output is cut and says how much was left out. The approval request itself keeps up to 32 KiB. The continuation message is stored in the conversation like any other turn, so what it carries is replayed to the model on later turns while it stays inside the history window: keep this as small as the tools you gate allow. 0 sends no output |
app.approvals.continuation.max-arguments-chars |
APP_APPROVALS_CONTINUATION_MAX_ARGUMENTS_CHARS |
2000 |
Longest argument JSON shown in the continuation message |
app.approvals.continuation.max-sticky-tools |
APP_APPROVALS_CONTINUATION_MAX_STICKY_TOOLS |
20 |
Tools the conversation already used (plus the approved one) that stay on offer for the continuation turn whatever the goal matches, so the next step's tools are not trimmed away |
app.approvals.continuation.sticky-tools-window |
APP_APPROVALS_CONTINUATION_STICKY_TOOLS_WINDOW |
PT24H |
How far back in the tool-call log "tools already used" looks |
app.approvals.continuation.claim-ttl |
APP_APPROVALS_CONTINUATION_CLAIM_TTL |
P7D |
How long "this decision was already continued" is remembered in Redis; a repeat of the decision (retry, duplicate event, second replica) inside it starts nothing |
app.approvals.continuation.turn-timeout |
APP_APPROVALS_CONTINUATION_TURN_TIMEOUT |
PT10M |
Wall-clock limit of the continuation turn started for a chat session |
app.approvals.continuation.max-other-pending |
APP_APPROVALS_CONTINUATION_MAX_OTHER_PENDING |
10 |
How many other still-pending actions of the same conversation are listed to the agent, so it does not request them again |
app.approvals.continuation.jobs-continue-after-denial |
APP_APPROVALS_CONTINUATION_JOBS_CONTINUE_AFTER_DENIAL |
false |
Agent jobs. false: a denied approval ends the job as APPROVAL_REJECTED. true: the job resumes with the denial as the call's result so the agent can reach the goal another way (still bounded by app.agent-jobs.approval.max-parks-per-job). Chat sessions always continue after a denial |
app.approvals.continuation.result-shortened-suffix |
(none) | text in application.yml |
Group app.approvals.continuation (model-facing, editable). Appended to a shortened result; {omitted} is the number of characters left out |
app.approvals.continuation.approver-unknown |
(none) | a reviewer |
Group app.approvals.continuation (model-facing). Names the decider in the continuation message and the denial note when none is recorded |
app.approvals.continuation.no-reason-text |
(none) | no reason was given |
Stands in for the reviewer's reason when a call is denied without one |
app.approvals.continuation.result-not-recorded-text |
(none) | text in application.yml |
The result given to a resumed agent job for an approved call whose output was never recorded (the replay may have been interrupted) |
app.approvals.continuation.denied-note |
(none) | text in application.yml |
The bracketed note appended to the conversation history when a call is denied, the counterpart of the "approved action ... completed" note. {tool}, {approver}, {reason} |
app.approvals.continuation.completed-note |
(none) | text in application.yml |
The bracketed note appended to the conversation history when an approved call ran, whichever executor ran it (tool replay, code-workspace run or apply, channel send). {tool}, {result} |
app.approvals.continuation.failed-note |
(none) | text in application.yml |
The note appended when an approved call failed for good (not for a failure that will be retried). {tool}, {failure} |
app.approvals.continuation.completed-no-output-text |
(none) | text in application.yml |
What the agent is told for an approved call that ran but produced no output (a message sent, say) |
app.approvals.continuation.max-note-result-chars |
APP_APPROVALS_CONTINUATION_MAX_NOTE_RESULT_CHARS |
500 |
Longest output or failure text put into a history note. A note is re-sent on every later turn of the conversation, so it is much tighter than max-result-chars, which governs the one continuation message that carries the full result |
app.approvals.continuation.max-note-arguments-chars |
APP_APPROVALS_CONTINUATION_MAX_NOTE_ARGUMENTS_CHARS |
160 |
Longest argument summary in the history note. The note is stored as a platform tool record on an assistant turn with no text (the model reads it on later turns; the person's transcript and export never show it). Capped at 500 by the credential scrub |
app.approvals.continuation.note-approval |
(none) | text in application.yml |
The approval detail of that tool record, after the tool record's approval-detail wording. {requestId}, {approver} |
app.approvals.continuation.other-pending-header |
(none) | text in application.yml |
Introduces the list of other actions of the task still waiting for a decision |
app.approvals.continuation.completed-message |
(none) | text in application.yml |
The body of the message the agent is sent when the approved call ran: it was approved and has run, the user's original request is unchanged ({goal}), the call ({tool}, {arguments}), its output in <tool_result> ({result}, treated as data), {otherPending}, and to continue the original task. The <approval_result ...> tag around it and the <tool_result> wrapper are written by the code |
app.approvals.continuation.failed-message |
(none) | text in application.yml |
The same when the approved call could not be completed: the action did not take effect; correct the cause, take another route or tell the user |
app.approvals.continuation.denied-message |
(none) | text in application.yml |
The same when a human denied the call ({result} is the reviewer's reason): it did not run; do not ask again with the same arguments; reach the goal another way or tell the user |
app.tools.classification.unclassified-action |
TOOL_UNCLASSIFIED_ACTION |
allow |
what a tool with no classification row gets: allow (counted), require-approval or deny |
Which approvals continue the task. The platform carries the original task on after a human decides (or the request expires) for every action an agent turn asked for, not only a replayed MCP tool: the mcp_tool.* executor (which includes the Assistant's folder tools and local shell) does it itself; for everything else HumanApprovalGateService reports the outcome through ApprovalOutcomePort and ApprovalOutcomeFollowUp writes the history note and starts the turn. That covers codeintel.workspace.run, codeintel.workspace.apply and the channel sends an agent asks for (channel_send_message); the request must have been recorded by a tool through evaluateInTurn (it then carries the goal and the scoped history id). Not continued, on purpose: actions raised from an admin screen, a webhook, an inbound channel flow or the scheduler (no agent turn waits for them), a request the tenant's policy auto-approves (it ran inside the turn that asked), workflow approval nodes (they resume their own run), payment mandates (the result is a payment link the person completes later), agent proposals (agent.proposal starts a job), agent-job sessions (the job machinery resumes the job) and the embeddable SDK's executors (the host owns the conversation). A failure that will be retried is reported only when the last attempt fails.
Browser follower for approval continuations (not configurable). After a person decides a parked tool call, the chat surfaces (Playground, Agent Lab chat, Assistant) follow the continuation turn core starts by polling the stored conversation (GET /api/agents/{id}/sessions/{sid}/transcript) and hold their input back meanwhile. The cadence lives in web/app/components/agents/approvalContinuation.ts, not in a core property: poll every 2 s (CONTINUATION_POLL_MS); give up waiting for the continuation to start after 20 s (CONTINUATION_START_WAIT_MS: core starts it inside the approve/reject call, so a task that was not restarted - a job session, app.approvals.continuation.enabled=false, a retryable failure - is reported instead of waited on); stop following a started continuation after 10 min (CONTINUATION_MAX_WAIT_MS, kept equal to the default of app.approvals.continuation.turn-timeout; raise it together with that property). The three user-facing lines it shows are in web/messages/en.json under toolApproval (continuing, continueNotStarted, continueTimedOut).
Scale and reliability limits¶
Bounds that keep one busy or misbehaving tenant from starving the rest of a core process. All are
core settings (core/app-config); the environment variable is the override. Defaults are chosen so a
small install behaves as before; the two off by default features are opt-in.
| Variable | Default | Effect |
|---|---|---|
APP_SCHEDULING_MAX_CONCURRENT_LOCKED_JOBS |
4 |
most scheduled jobs that may hold a Postgres advisory lock (one pinned connection each) at the same time in one JVM, so they cannot exhaust the connection pool; cron, long-running and critical jobs are exempt |
INBOUND_EXECUTOR_MAX_CONCURRENT / INBOUND_EXECUTOR_PER_TENANT_MAX_CONCURRENT |
64 / 16 |
channel webhooks (Meta, Twilio, Telegram) run their agent turn on a bounded executor: total and per-tenant concurrent turns |
INBOUND_EXECUTOR_QUEUE_CAPACITY / INBOUND_EXECUTOR_QUEUE_PER_TENANT / INBOUND_EXECUTOR_QUEUE_WAIT_MS |
64 / 16 / 5000 |
when every slot is busy a turn waits in a bounded FIFO queue for up to this long |
INBOUND_EXECUTOR_OVERFLOW_MAX / INBOUND_EXECUTOR_OVERFLOW_PER_TENANT_MAX |
64 / 16 |
extra turns admitted past the queue for cheap paths that take no slot |
INBOUND_EXECUTOR_SHED_MODE |
retry |
what an overloaded webhook returns: retry answers 503 so the provider redelivers, ack answers 200 and releases the dedup claim, so that message is dropped (the provider does not redeliver). Twilio and WhatsApp QR, whose providers do not redeliver, are never shed: they are admitted as overflow, and run on the request thread if even that is full. Caps are per core replica |
APP_TENANT_IDLE_SKIP_ENABLED |
false |
opt-in. Two per-tenant pollers (the webhook delivery relay and agent-job dispatch) skip tenants that had no work last tick, with exponential back-off |
APP_TENANT_IDLE_SKIP_MAX_TICKS / APP_TENANT_IDLE_SKIP_MAX_DURATION |
3 / 60s |
upper bound on how many ticks or how long an idle tenant is skipped, so fresh work waits at most that long (30 s extra with the defaults on a 10 s sweep; see Tenant sweep cost). Counter vegaduta_tenant_sweep_tenants_total{job,outcome} shows visits and skips |
KNOWLEDGE_INGESTION_MAX_JOBS_PER_TENANT_PER_TICK / KNOWLEDGE_INGESTION_TICK_BUDGET_SECONDS |
2 / 120 |
fair share and wall-clock budget for knowledge ingestion per tick; tenants rotate across ticks |
KNOWLEDGE_INGESTION_CLAIMING_ENABLED / KNOWLEDGE_INGESTION_CLAIM_LEASE_SECONDS / KNOWLEDGE_INGESTION_CLAIM_MAX_ATTEMPTS |
false / 300 / 3 |
opt-in. Per-job claiming for knowledge ingestion: no poll-wide advisory lock, so ingest replicas can work different jobs at once; a job whose runner stopped heartbeating for the lease is re-run by another, at most MAX_ATTEMPTS times in total, then it is marked FAILED (so a job that kills its pod each time cannot loop forever). Keep the lease well above a worst-case pause. Rolling out: a replica with the flag off still starts only QUEUED jobs (conditional update), so it cannot double-run a claimed job, but it does not heartbeat; a job it runs for longer than the lease can be reclaimed by a claiming replica. Mixing claim-mode and legacy replicas is unsupported except for the duration of one rollout: turn the flag on for all ingest replicas in one rollout, or scale to one replica while switching. The heartbeat statement has a app.knowledge.ingestion.claiming.heartbeat-timeout-seconds (default 5) query timeout, because one heartbeat thread serves every in-flight job of a pod and a single hung call would otherwise let all their leases expire together; a timed-out heartbeat is logged and retried on the next beat |
KNOWLEDGE_REEMBED_MAX_JOBS_PER_TENANT_PER_TICK / KNOWLEDGE_REEMBED_TICK_BUDGET_SECONDS |
1 / 300 |
the same for re-embedding |
APP_RETENTION_PURGE_ENABLED |
false |
opt-in. Batched per-tenant purge of published outbox rows, delivered or dead webhook deliveries and old notifications (app.retention.purge.* sets the ages, batch size and a 120 s tick budget) |
APP_RETENTION_PURGE_INTERVAL / APP_RETENTION_PURGE_INITIAL_DELAY |
6h / 30m |
how often the purge ticks and how long after boot the first tick waits. The ages are fixed properties, not env-bound: app.retention.purge.outbox-published-days (7), webhook-delivery-days (30), notification-days (90), batch-size (5000), max-batches-per-table (20), pause-ms (50), max-tick-seconds (120) |
APP_RETENTION_PURGE_DRY_RUN |
true |
when the purge is enabled, count what it would delete and log it instead of deleting; set false to delete |
PRIVACY_RETENTION_ENABLED / PRIVACY_RETENTION_CRON |
true / 0 17 2 * * * (UTC) |
the nightly tenant retention job for the DPDP-oriented controls (obligations). On by default, but it deletes nothing for a tenant until that tenant enables a rule (PUT /api/tenant/privacy/retention-policies); an enabled rule with no period uses 365 days. false stops the job for every tenant |
APP_LLM_SPEND_COUNTER_RECONCILE_INTERVAL |
1h |
how often the billing-pool job LlmSpendCounterReconciler recomputes each tenant's llm_spend_month counter (current and previous UTC month) from llm_call_log and corrects drift left by pods on an older image; 0 or off disables. Also app.llm.spend-counter.reconcile-max-tenants-per-tick (200) and reconcile-max-tick-seconds (30) cap the work per one-minute tick |
LIST_DEFAULT_CAP |
false |
opt-in. Read straight from the process environment (or the JVM property -Dagenticai.list.default-cap=true), not from application.yml, so it is not a Spring property and Helm core.env or Compose core.env is the place to set it. true caps list requests that send no limit at each endpoint's default (approvals 200, contacts 500, documents 500, runs 200). Off keeps the complete list for limit-less callers (mobile apps, the SDKs, the outreach MCP server). See List endpoint paging |
RESILIENCE_CB_ENABLED |
true |
circuit breakers are on by default; set false to restore the previous behaviour. The bulkhead (RESILIENCE_BULKHEAD_ENABLED, false; RESILIENCE_BULKHEAD_MAX_WAIT, 0s) stays off, and applies to a dependency only when app.resilience.bulkhead.instances.<family>.max-concurrent is set above 0. Tuning: RESILIENCE_CB_CONSECUTIVE_FAILURES (5, failures in a row that open it; 0 = off), RESILIENCE_CB_FAILURE_RATE_PERCENT (50) over at least RESILIENCE_CB_MINIMUM_CALLS (10) of the last RESILIENCE_CB_WINDOW_SIZE (20) calls within RESILIENCE_CB_WINDOW (60s), RESILIENCE_CB_OPEN_DURATION (30s, how long an open circuit sheds), RESILIENCE_CB_HALF_OPEN_CALLS (2, probe calls), RESILIENCE_CB_HALF_OPEN_PROBE_TIMEOUT (30s), and RESILIENCE_CB_HOSTED_LLM_HALF_OPEN_PROBE_TIMEOUT (180s, longer because a probe settles at the first token) |
List endpoints for workflow runs, channel contacts, knowledge documents and approvals accept
optional limit and offset parameters with a default cap and a hard maximum; internal callers
stay unbounded. See List endpoint paging.
Keys the code reads that this guide did not name¶
| Property | Default | Meaning |
|---|---|---|
app.knowledge.vector-index.initial-delay-ms |
180000 |
Delay after startup before the first pgvector index maintenance pass (kept off the startup path). |
app.knowledge.vector-index.interval-ms |
21600000 |
Time between pgvector index maintenance passes (6 hours). |
app.llm.spend-counter.reconcile-initial-delay-ms |
120000 |
Delay after startup before the first tick of the spend-counter reconciler (the pass itself runs every app.llm.spend-counter.reconcile-interval). |
app.llm.spend-counter.reconcile-tick-ms |
60000 |
How often the reconciler checks whether a pass is due; a pass in progress continues on each tick. |
app.mcp.max-stdio-connections-per-tenant |
5 |
How many local (STDIO) MCP servers one workspace may keep connected at once; the next one is refused with a message to disable another or ask the operator to raise this. |
app.mcp.registry-store |
memory |
memory: each replica connects MCP servers on its own. redis: replicas share an ownership registry so one registration has one live client (set this when running more than one replica). |
app.mcp.registry-heartbeat-ms |
15000 |
With registry-store=redis: how often a replica renews its leases and its own registration. |
app.mcp.registry-poll-ms |
250 |
With registry-store=redis: how often a replica drains its command queue from the other replicas. |
app.mcp.registry-adopt-interval-ms |
60000 |
With registry-store=redis and exclusive clients: how often connected registrations with no live owner (the owner died, its lease expired) are adopted by a replica. |
app.sandbox.data-analysis.artifact-sweep-ms |
600000 |
How often charts, workbooks and scripts older than app.sandbox.data-analysis.artifact-ttl-hours are deleted from object storage. |
app.agent.share.report-revoke-threshold |
3 |
Reports after which a public agent share link is revoked automatically. |
app.x402.allowed-payees |
empty (every payment refused) | Comma-separated EVM addresses an x402 payment may be made to; the payee comes from the server the model called, so nothing is paid until you list the addresses you trust. |
app.x402.tenant-daily-budget-usdc |
5 |
Most USDC one workspace may spend through x402 in a rolling 24 hours. A value that is not a number refuses every payment. |
app.sidecar.shared-secret |
empty (falls back to SIDECAR_SHARED_SECRET) |
Shared secret the knowledge sidecar expects from the platform (the value is not reproduced here). |
app.agents.default-guardrails |
${APP_AGENTS_DEFAULT_GUARDRAILS:jailbreak} |
Guardrails a new agent starts with, comma-separated; empty means none. |
app.chat.directives.memory.enabled |
${APP_CHAT_DIRECTIVES_MEMORY_ENABLED:true} |
Operator kill switch for the standing memory directive in the system prompt; there is no per-agent or per-tenant override because the standing directives are safety rails. |
app.tools.auto-selection.max-tools-per-turn |
${TOOL_AUTO_SELECTION_MAX_TOOLS:12} |
Most tools offered to the model in one turn when automatic tool selection is on (24 in the personal profile). |
app.tools.effect-journal.enabled |
${TOOL_EFFECT_JOURNAL_ENABLED:true} |
Records write and send tool calls while they are in flight, so a job resumed after a restart does not repeat them. Off disables resume-from-journal. |
app.guardrails.run-budget.usd |
${AGENT_RUN_BUDGET_USD:2.0} |
Platform cap, in USD, on what one agent run may spend; 0 means no platform cap. The code default is the same 2.0, so a context without application.yml is capped too. |
app.memory.blocks.enabled |
${APP_MEMORY_BLOCKS_ENABLED:true} |
Whether memory blocks (short named notes, including per-project blocks) are read and given to agents; the size limits are app.memory.blocks.max-chars-per-block, max-project-blocks and max-total-chars. |
app.context.compaction.enabled |
${APP_CONTEXT_COMPACTION_ENABLED:true} |
Whether a long prompt is compacted (older turns folded into a summary) before it overflows the model's window. On by default since 2026-10-23 (owner decision; a prompt inside the window is untouched); see app.auxiliary-model.compaction for the model that writes the summary. |
Service environment¶
Environment variables read by the Python and Node services in services/ (not by core). On/off
switches and service URLs are in Feature flags for optional services;
this section lists the tuning, capacity and isolation settings. The defaults are the ones in each service's
settings module (app/settings.py, app/config.py or server.js) and the Compose file sets only a few of
them. Set one in deploy/compose/.env only if the Compose service passes it through; otherwise add it to
that service's environment: block (or the chart's env for Helm). Each service's own README has the
full list for it, including the auth and URL variables; the rows here are the ones that were not written
down before. Names ending in _KEY, _PASSWORD or _TOKEN are secrets: set them from the secret store,
never in a tracked file.
sidecar (ML helper, port 8001)¶
| Variable | Default | Effect |
|---|---|---|
SIDECAR_LOG_LEVEL |
INFO |
Python log level |
EMBED_QUEUE_TIMEOUT_S |
30 |
how long an embed request waits for a free slot before the sidecar fails it fast (a 503-shaped error the caller retries) |
RERANK_MAX_CONCURRENT / RERANK_QUEUE_TIMEOUT_S |
1 / 30 |
concurrent rerank passes and how long one waits for a slot; raise the first only with spare CPU |
WHISPER_CPU_THREADS / WHISPER_NUM_WORKERS |
1 / 2 |
speech-to-text CPU threads per transcription and concurrent transcriptions |
WHISPER_LIVE_RESERVED_WORKERS |
1 |
transcription workers kept for live calls so document ingestion cannot starve them; ignored (0) when WHISPER_NUM_WORKERS is 1 |
TESSERACT_CMD |
unset | path to the Tesseract executable for OCR when it is not on PATH |
FFMPEG_BINARY / FFPROBE_BINARY |
ffmpeg / ffprobe |
executables used by the render endpoints |
RENDER_TIMEOUT_S |
3600 |
wall-clock limit for one render |
RENDER_MAX_FETCH_MB |
512 |
largest remote media file a render will download |
SARVAM_SPEAKER |
empty | Sarvam voice; empty lets Sarvam pick the default for the model. Speaker names are per model, so clear it when changing SARVAM_MODEL |
SARVAM_MAX_CHARS_PER_REQUEST |
1400 |
text is split into chunks of at most this many characters per Sarvam request |
SARVAM_MAX_CONCURRENT / SARVAM_TIMEOUT_S |
1 / 60 |
concurrent Sarvam requests and the per-request timeout |
code-intel (repository indexing, port 8090)¶
| Variable | Default | Effect |
|---|---|---|
CODE_INTEL_GIT_ALLOWED_SCHEMES |
https |
URL schemes a tenant-supplied git_url may use (comma-separated). file, ssh and git would reach this container's own disk or internal hosts; widen only for a trusted install |
CODE_INTEL_ALLOWED_AUTH_ENV |
empty | extra environment variable names a repository may name as auth_env; CODE_INTEL_GIT_TOKEN is always allowed |
CODE_INTEL_AUTH_ENV_URL_PREFIXES |
empty | git_url prefixes to which an auth_env credential may be sent; empty means auth_env is unusable, so a tenant cannot make the operator's token travel to their own remote |
CODE_INTEL_ENFORCE_REPO_LIMITS |
empty (off) | true enforces the two repository limits below; off keeps existing repositories working |
CODE_INTEL_MAX_REPO_MB / CODE_INTEL_MAX_REPO_FILES |
500 / 50000 |
per-repository size and file count, enforced only when the switch above is true |
CODE_INTEL_TENANT_QUOTA_MB |
0 (off) |
total checkout size per tenant |
CODE_INTEL_GITDIR_ROOT |
<system temp dir>/code-intel-gitdirs |
where git directories are kept, apart from the checkouts (tests, operators) |
CODE_INTEL_GIT_TIMEOUT |
300 |
seconds allowed for one git operation |
CODE_INTEL_MAX_HEAVY_JOBS / CODE_INTEL_MAX_TENANT_HEAVY_JOBS |
4 / 2 |
concurrent index, clone and graph-build jobs in total and per tenant |
CODE_INTEL_MAX_QUEUED_HEAVY_JOBS / CODE_INTEL_HEAVY_QUEUE_WAIT |
8 / 900 |
background jobs allowed to wait for a slot, and how many seconds each waits |
CODE_INTEL_HEAVY_RETRY_AFTER |
30 |
seconds in the Retry-After answered to a refused heavy job |
CODE_INTEL_EMBED_MODEL |
BAAI/bge-small-en-v1.5 |
default embedding model (with CODE_INTEL_EMBEDDER) |
CODE_INTEL_ALLOWED_EMBED_MODELS |
empty | models a repository may request besides the default; empty means no per-repository overrides |
CODE_INTEL_EMBEDDER_CACHE |
2 |
embedder models kept loaded |
OPENAI_API_KEY |
not-needed-for-local placeholder |
read only when the openai embedder is selected; a secret |
CODE_INTEL_RERANK_ENABLED |
false |
cross-encoder reranking of search results |
CODE_INTEL_RERANK_MODEL |
cross-encoder/ms-marco-MiniLM-L-6-v2 |
the reranking model |
CODE_INTEL_RERANK_MAX_CONCURRENT / CODE_INTEL_RERANK_QUEUE_TIMEOUT_S |
1 / 30 |
the same pair as in the sidecar |
CODE_INTEL_DOCKER_USER |
auto |
user of the sandbox container in run mode docker: auto is this process's own non-root uid:gid (never root), an explicit uid:gid needs the workspaces writable by that uid, empty means the image default (root, not recommended) |
CODE_INTEL_DOCKER_READ_ONLY |
true |
read-only root filesystem in the sandbox container |
CODE_INTEL_DOCKER_TMPFS_SIZE / CODE_INTEL_DOCKER_TMPFS_EXEC |
512m / true |
size of the RAM-backed /tmp and whether it allows execution (build tools need it) |
CODE_INTEL_DOCKER_CAP_DROP |
ALL |
capabilities dropped from the sandbox container |
CODE_INTEL_DOCKER_NO_NEW_PRIVILEGES |
true |
no-new-privileges on the sandbox container |
CODE_INTEL_DOCKER_NANO_CPUS |
1000000000 |
CPU limit of the sandbox container in billionths of a CPU (1 CPU) |
CODE_INTEL_E2B_TEMPLATE |
base |
E2B template when the run mode is e2b (needs E2B_API_KEY, a secret) |
app-builder (port 8091)¶
| Variable | Default | Effect |
|---|---|---|
APP_BUILDER_KEYCLOAK_AUDIENCE |
unset | required aud of a Keycloak token; the audience is not checked while unset |
APP_BUILDER_KEYCLOAK_ALLOWED_AZP |
agentic-ai-core,agentic-ai-web |
Keycloak clients whose tokens are accepted (azp). Empty refuses every bearer token; there is no wildcard. The internal-token path does not use it |
APP_BUILDER_CODE_INTEL_TIMEOUT |
60 |
seconds to wait for a code-intel call |
APP_BUILDER_MAX_FIX_ATTEMPTS |
3 |
automatic repair attempts after a failed build or test (0 to 6); each is one more model call |
APP_BUILDER_PUBLIC_URL |
empty | public base URL shown to users for the generated apps' data API; set it in production, because the request's own URL is unreliable behind a proxy |
APP_BUILDER_DATA_API_ENABLED |
true |
operator switch for the generated apps' data API |
APP_BUILDER_DATA_API_MAX_CONNS |
8 |
database connections the data API proxy may use at once (1 to 12); beyond it, 503 |
APP_BUILDER_DATA_API_STATEMENT_TIMEOUT_MS |
5000 |
statement timeout of the data API (500 to 30000) |
APP_BUILDER_DATA_API_ANON_RPM / APP_BUILDER_DATA_API_ANON_RPM_PER_IP |
600 / 120 |
requests per minute for an anonymous key, and per client IP (best effort, the IP comes from proxy headers) |
APP_BUILDER_DATA_API_SERVICE_RPM |
1200 |
requests per minute for a service key |
APP_BUILDER_PREVIEW_DOMAIN |
preview.localhost |
domain under which previews are served (<project>.<domain>); wildcard DNS and a tunnel hostname are a manual step |
APP_BUILDER_IMPORT_MAX_FILES / APP_BUILDER_IMPORT_MAX_BYTES |
5000 / 104857600 |
size limits when importing an existing repository; a larger tree is refused with 413 and a hint to use subdir |
Android builds of a generated Capacitor app read ANDROID_KEYSTORE_PATH, ANDROID_KEYSTORE_PASSWORD,
ANDROID_KEY_ALIAS and ANDROID_KEY_PASSWORD (the upload signing key; secrets) and VERSION_CODE (default
1) and VERSION_NAME (default 1.0.0) in the build environment, through
scripts/android-release-config.mjs of the template.
tool-sandbox (port 8095)¶
| Variable | Default | Effect |
|---|---|---|
TOOL_SANDBOX_RUN_UID / TOOL_SANDBOX_RUN_GID |
unset (the image sets 5001) |
the one unprivileged uid and gid tenant code runs as |
TOOL_SANDBOX_RUN_UID_RANGE |
unset | inclusive range such as 20000-29999; each run gets its own uid from it, so two concurrent runs cannot signal or read each other. Unset or malformed falls back to the single uid and logs a warning |
TOOL_SANDBOX_ALLOW_MISSING_CAP_KILL |
false |
without CAP_KILL a timed-out run cannot be killed, so the service refuses to start; true starts it anyway with weaker enforcement |
TOOL_SANDBOX_WORK_ROOT / TOOL_SANDBOX_CACHE_ROOT / TOOL_SANDBOX_HARNESS_ROOT |
/tmp/sandbox-work / /tmp/sandbox-cache / /opt/tool-sandbox (the image uses /var/sandbox/work and /var/sandbox/cache) |
per-run working directories, the compiled-artifact cache and the harness files |
TOOL_SANDBOX_MAX_INPUT_BYTES / TOOL_SANDBOX_MAX_OUTPUT_BYTES / TOOL_SANDBOX_MAX_RESULT_BYTES |
262144 / 65536 / 262144 |
size limits for the input, captured output and result |
TOOL_SANDBOX_DEFAULT_TIMEOUT_MS / TOOL_SANDBOX_DEFAULT_MEMORY_MB |
5000 / 128 |
defaults for a run that does not ask for more |
TOOL_SANDBOX_COMPILE_TIMEOUT_MS / TOOL_SANDBOX_COMPILE_MEMORY_MB |
30000 / 512 |
separate, larger budget for compiling Java |
TOOL_SANDBOX_MAX_CACHE_ENTRIES |
64 |
compiled artifacts kept per tenant, oldest evicted first |
TOOL_SANDBOX_JAVAC / TOOL_SANDBOX_JAVA / TOOL_SANDBOX_PYTHON |
javac / java / python3 |
toolchain executables |
workspace-runner (the Assistant's build container, port 8090)¶
| Variable | Default | Effect |
|---|---|---|
WORKSPACE_RUNNER_PORT |
8090 |
listen port |
WORKSPACE_ROOT |
/workspace |
the projects folder inside the container |
WORKSPACE_RUNNER_SECRET_FILE |
unset | file holding the shared secret (preferred, so it is never in an environment); WORKSPACE_RUNNER_SECRET is the fallback. The service refuses to start without one |
WORKSPACE_RUNNER_MAX_CONCURRENT |
4 |
commands running at once |
WORKSPACE_RUNNER_MAX_OUTPUT_CHARS |
200000 |
output kept per command |
WORKSPACE_RUNNER_KILL_GRACE_MS |
5000 |
wait between the termination signal and a forced kill |
WORKSPACE_RUNNER_SETTLE_MS |
4000 |
how long a started dev server is watched for its first output before the call returns |
WORKSPACE_RUNNER_PREVIEW_PORT_FIRST / WORKSPACE_RUNNER_PREVIEW_PORT_COUNT |
13210 / 10 |
the port range dev servers get. Keep it equal to the range Compose publishes (13210-13219 in personal.yml and lite.yml); changing one side only breaks previews silently |
MAVEN_OPTS / GRADLE_USER_HOME |
empty | passed through to the commands the Assistant runs (WORKSPACE_RUNNER_MAX_TIMEOUT_SECONDS is in .env.example) |
voice-agent (LiveKit worker, port 8081)¶
| Variable | Default | Effect |
|---|---|---|
LIVEKIT_AGENT_NAME |
empty | leave empty: the worker uses implicit dispatch; setting it without changing how tokens are minted means the worker is never offered a job |
ELEVEN_API_KEY |
unset | fallback for ELEVENLABS_API_KEY (a secret) |
ELEVENLABS_MODEL |
eleven_turbo_v2_5 |
ElevenLabs model |
SARVAM_SPEAKER |
empty | as in the sidecar |
PERCEPTION_SCENE_DIFF_THRESHOLD |
0.10 |
how much a camera frame must change (0 to 1, mean grayscale difference) before the agent looks again; raise it for less chatter |
VOICE_AGENT_VAD_MIN_SILENCE_DURATION_S |
0.35 |
silence that ends a turn; raise for slow speakers or noisy rooms |
VOICE_AGENT_LOG_LEVEL |
INFO |
log level |
VOICE_AGENT_DEV_STATIC_BEARER_TOKEN |
empty | local testing only: never set it in a deployment, it makes every call authenticate as one account |
rl-trainer (port 8095)¶
| Variable | Default | Effect |
|---|---|---|
RL_TRAINER_KEYCLOAK_AUDIENCE |
unset | required aud of a Keycloak token; not checked while unset |
RL_TRAINER_KEYCLOAK_ALLOWED_AZP |
agentic-ai-core,agentic-ai-web |
accepted Keycloak clients; empty refuses every bearer token |
mcp-stdio-runner¶
| Variable | Default | Effect |
|---|---|---|
MCP_STDIO_RUNNER_SWEEP_INTERVAL_MS |
30000 |
how often the runner checks for children past MCP_STDIO_RUNNER_IDLE_TTL_MS or MCP_STDIO_RUNNER_MAX_LIFETIME_MS; 0 turns the check off, so neither limit is enforced |
Compose-only variables¶
deploy/compose/.env.example is the complete, grouped reference for the
Compose tiers; the groups below are not Spring properties and appear only
there.
| Group | Variables | Notes |
|---|---|---|
| Social login | GOOGLE_CLIENT_ID/_SECRET, GITHUB_CLIENT_ID/_SECRET, MICROSOFT_CLIENT_ID/_SECRET |
Keycloak identity providers, reconciled into the realm |
KEYCLOAK_SMTP_* (verification and reset mail), ALERT_SMTP_* (Alertmanager) |
||
| Generated secrets | everything in SECRET_SPECS of scripts/vegaduta (database, Valkey, storage, service tokens, Langfuse, SEARXNG_SECRET, WORKSPACE_RUNNER_SECRET, WHATSAPP_QR_BRIDGE_SECRET), plus every secret-like ${X:?} any file under deploy/compose requires (for example the DefectDojo DD_* secrets) |
scripts/vegaduta secrets (and vegaduta.ps1 secrets) fills the empty ones. Before starting, up runs docker compose config -q, generates any secret Compose still reports missing and stops naming any other variable that is empty |
| Console flags | WEB_MCP_APPS, WEB_TRUSTED_IMAGE_HOSTS, WEB_EDGE_AGENTS, WEB_FREE_MODE, WEB_DEPLOY_ENV |
mapped to the matching NEXT_PUBLIC_* at runtime |
| Tracing | LANGFUSE_* |
the tracing profile |
| Resource limits | *_MEM_LIMIT, CODE_INTEL_DOCKER_PIDS_LIMIT |
per-container memory ceilings |
| Network | BIND_ADDR (default 127.0.0.1), *_HOST_PORT |
host port bindings. Lite and Personal publish core directly with no reverse proxy: with BIND_ADDR=0.0.0.0 anyone on the network can read /actuator/prometheus (request rates per endpoint, LLM usage per provider and model; no secrets) unless you set APP_ACTUATOR_SCRAPE_TOKEN and APP_ACTUATOR_SCRAPE_TOKEN_REQUIRED=true in deploy/compose/core.env |
Appendix: configuration property reference by module¶
This appendix lists every core/ configuration property (from application*.yml, @Value and
@ConfigurationProperties) that the sections above do not name. It was written from the code with
scripts/ci/gen-config-reference.py as the index; the complete machine-generated list, documented or not,
is CONFIG-REFERENCE.generated.md. Columns:
- Default: the literal default in the
@Valueexpression orapplication*.yml(an empty cell means no literal default was found;${NAME:x}is an environment variable with defaultx). Values for credential-like properties are not reproduced. - Applies to:
all shapeswhen the key is inapplication.yml;profile Xwhen it is inapplication-X.yml; otherwise the property is read by a class of the module, so it takes effect in every shape (api, jobs, worker) that loads that module. Where a profile such asshape-jobsmoves the owning scheduler elsewhere, the setting applies to the process that runs it. - Meaning: the comment or javadoc on the declaration, shortened.
see source: pathmeans no description exists next to the declaration, so read the named file rather than rely on a guess. - A name without a dot-suffix (a bare prefix such as
app.a2a) is the@ConfigurationPropertiesprefix; its fields are listed below it. - Relaxed binding applies:
app.foo-barcan be set asAPP_FOO_BAR.
Keep this appendix current: when you add a property, document it in the section that fits, or add it here; gen-config-reference.py --check (run by make gates) fails on an undocumented new property.
The Default column is read from the code, not typed: make config-docs (scripts/docs/gen-config-tables.py) lists every default in this guide that renders broken (", " + CONST + ", (set in source) for a key whose default the code knows), differs from application*.yml / @Value / @ConfigurationProperties, or is missing, and every key no document names; make config-docs-check fails on them; make config-docs-write rewrites only the default cell of the offending Appendix rows. Credential-like defaults are never reproduced.
Module admin (3)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.identity.auth0.client-secret |
${AUTH0_CLIENT_SECRET:} |
profile security, shapes that load the module |
see source: core/admin/src/main/java/com/agenticai/core/admin/auth0/Auth0UserAdminClient.java |
app.keycloak.client-secret |
${KEYCLOAK_CLIENT_SECRET:} |
profile security, shapes that load the module |
see source: core/admin/src/main/java/com/agenticai/core/admin/keycloak/KeycloakAdminClient.java |
app.security.permission-cache-ttl |
10s |
all shapes | Filter-chain hot-path cache (2026-09-24 scale work): the time UserPermissionService may serve one user's effective stored grants to GranularPermissionConverter without re-querying. |
Module agents (443)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.a2a |
shapes that load the module | The app.a2a.* config block for the inbound Agent2Agent (A2A) server surface (A2aAgentCardController, A2aJsonRpcController). | |
app.a2a.client.allow-insecure-loopback |
${APP_A2A_CLIENT_ALLOW_INSECURE_LOOPBACK:false} |
shapes that load the module | Permit plain http to a loopback host. |
app.a2a.client.allowed-hosts |
${APP_A2A_CLIENT_ALLOWED_HOSTS:} |
shapes that load the module | Egress allow-list of hosts (exact agents.example.com or *.example.com for subdomains only). |
app.a2a.blocking-timeout |
shapes that load the module | Upper bound a BLOCKING SendMessage (and each streamed chunk wait) holds the request thread for. | |
app.a2a.client.connect-timeout |
${APP_A2A_CLIENT_CONNECT_TIMEOUT:5s} |
shapes that load the module | Group app.a2a: The app.a2a.* config block for the inbound Agent2Agent (A2A) server surface (A2aAgentCardController,... (see source: core/agents/src/main/java/com/agenticai/core/agents/a2a/A2aProperties.java) |
app.a2a.enabled |
${APP_A2A_ENABLED:true} |
shapes that load the module | Platform-wide kill switch for the inbound A2A surface (agent cards, /api/a2a, REST). On by default; false removes all three controllers. |
app.a2a.heartbeat-interval |
${APP_A2A_HEARTBEAT_INTERVAL:10s} |
all shapes | Redis mode only: how often the owning replica refreshes the heartbeat of its non-terminal task records and checks them for a remote cancel. |
app.a2a.heartbeat-stale-after |
${APP_A2A_HEARTBEAT_STALE_AFTER:60s} |
all shapes | Redis mode only: a non-terminal record whose heartbeat is older than this and that belongs to ANOTHER instance id (a previous boot or a dead replica) is marked FAILED with "interrupted by restart". |
app.a2a.client.max-calls-per-turn |
${APP_A2A_CLIENT_MAX_CALLS_PER_TURN:5} |
shapes that load the module | Most a2a_call_agent calls one agent turn may make (OWASP ASI07/ASI08: a looping or injected agent must not fan out to remote agents without bound). |
app.a2a.max-concurrent-async-tasks |
32 |
shapes that load the module | How many non-blocking (returnImmediately/blocking:false) turns may run concurrently. |
app.a2a.client.max-response-bytes |
${APP_A2A_CLIENT_MAX_RESPONSE_BYTES:1048576} |
shapes that load the module | Hard cap on any single remote response body. |
app.a2a.client.max-result-chars |
${APP_A2A_CLIENT_MAX_RESULT_CHARS:20000} |
shapes that load the module | Cap on the text handed back to the model. |
app.a2a.max-tasks |
10000 |
shapes that load the module | Hard cap on tasks held in memory (across all tenants). |
app.a2a.client.max-wait |
${APP_A2A_CLIENT_MAX_WAIT:120s} |
shapes that load the module | Total time one tool call may wait for the remote task to finish. |
app.a2a.client.poll-interval |
${APP_A2A_CLIENT_POLL_INTERVAL:2s} |
shapes that load the module | Group app.a2a: The app.a2a.* config block for the inbound Agent2Agent (A2A) server surface (A2aAgentCardController,... (see source: core/agents/src/main/java/com/agenticai/core/agents/a2a/A2aProperties.java) |
app.a2a.public-base-url |
shapes that load the module | The externally reachable origin of THIS core service, e.g. | |
app.a2a.client.request-timeout |
${APP_A2A_CLIENT_REQUEST_TIMEOUT:30s} |
shapes that load the module | Per HTTP request (card, JWKS, send, poll). |
app.a2a.require-pinned-key |
false |
shapes that load the module | When true, boot FAILS if signing is enabled but no signing.private-key-pem is pinned - an ephemeral per-boot key gives every replica a different marketplace identity. |
app.a2a.client.require-signed-card |
${APP_A2A_CLIENT_REQUIRE_SIGNED_CARD:true} |
shapes that load the module | Refuse remote cards whose detached JWS does not verify (default true). |
app.a2a.signing |
shapes that load the module | Card signing - see A2aCardSigningService's javadoc. | |
app.a2a.task-store |
${APP_A2A_TASK_STORE:memory} |
all shapes | Where A2aTaskStore keeps task records: memory (default, per-JVM, today's behaviour) or redis (a tenant-scoped JSON snapshot mirrored to Redis with #taskTtl, so GetTask / CancelTask work from any core replica). |
app.a2a.task-ttl |
${APP_A2A_TASK_TTL:24h} |
all shapes | How long a finished task stays retrievable via GetTask / tasks/get. |
app.a2a.version |
shapes that load the module | The version field of the Agent Card. | |
app.agent-hooks.cache-ttl-ms |
15000 |
all shapes | How long a resolved hook set is cached per tenant+agent (a save invalidates it at once on this replica). |
app.agent-hooks.enabled |
true (false in personal; was false before 2026-10-23) |
all shapes | Group app.agent-hooks: Declarative agent lifecycle hooks (core/agents com.agenticai.core.agents.hooks): per-agent and... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/hooks/AgentHookConfigService.java) |
app.agent-hooks.hard-cap-ms |
5000 |
all shapes | Absolute ceiling on any single webhook hook's timeout, whatever the hook says. |
app.agent-hooks.latency-budget-ms |
2000 |
all shapes | Shared wall-clock budget for the synchronous hooks of one hook point (webhooks included). |
app.agent-hooks.webhook.allowed-hosts |
${AGENT_HOOKS_WEBHOOK_ALLOWED_HOSTS:} |
all shapes | Comma-separated hosts a webhook hook may call, in addition to each workspace's own list (exact host, or *.suffix for subdomains). |
app.agent-hooks.webhook.signing-secret |
${AGENT_HOOKS_WEBHOOK_SIGNING_SECRET:} |
all shapes | Optional HMAC-SHA256 signing key; when set, each event carries the X-VegaDuta-Signature header outbound tenant webhooks use. |
app.agent-import.enabled |
true |
all shapes | Group app.agent-import: Migration kits (core/agents importer): import an agent from another platform (OpenAI Assistants / Agents... (see source: core/agents-studio/src/main/java/com/agenticai/core/agents/importer/AgentImportService.java) |
app.agent-import.max-payload-bytes |
1048576 |
all shapes | Largest accepted payload (UTF-8 bytes of the exported JSON). |
app.agent-jobs |
shapes that load the module | The app.agent-jobs.* block (see application.yml). | |
app.agent-jobs.approval |
shapes that load the module | Read by the APPROVAL-PARKING half. | |
app.agent-jobs.approval.replay-grace |
${AGENT_JOB_APPROVAL_REPLAY_GRACE:PT90S} |
PT90S |
How long an approved-but-not-yet-executed approval (no result recorded) is left alone before the sweep resumes the parked job anyway. approve commits the decision and replays the tool afterwards, so resuming inside that window re-asks for the same gate or races the replay into a duplicate side effect. Raise it for tools that replay slowly. Was a constant |
app.agent-jobs.approval.expired-failure-message |
(none) | text in application.yml |
The failure message of a job whose blocking approval request expired (app.approvals.expiry) while jobs-continue-after-denial is false; the job fails as APPROVAL_TIMEOUT. {label} is the requested_by label of the request |
app.agent-jobs.quiesce-worker-wait |
${AGENT_JOB_QUIESCE_WORKER_WAIT:PT3S} |
PT3S |
How long a shutting-down replica waits for its interrupted job workers to unwind before it releases their ownership leases. Keep well inside the SIGTERM grace period (30 s). Was a constant |
app.agent-jobs.deadline-sweep-interval |
15s |
all shapes | Backs BOTH the deadline sweep and the orphan sweep. |
app.agent-jobs.default-timeout |
10m |
all shapes | Group app.agent-jobs: The app.agent-jobs.* block (see application.yml). (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/job/AgentJobProperties.java) |
app.agent-jobs.durability |
shapes that load the module | Read by the DURABILITY half (orphan recovery). | |
app.agent-jobs.durability.resume-from-journal |
false |
all shapes | Group app.agent-jobs.durability: Read by the DURABILITY half (orphan recovery). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/StepJournal.java) |
app.agent-jobs.heartbeat-interval |
15s |
all shapes | Group app.agent-jobs: The app.agent-jobs.* block (see application.yml). (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/job/AgentJobProperties.java) |
app.agent-jobs.interrupt-on-external-terminal |
true |
profile shape-api, profile shape-jobs-core, profile shape-jobs, shapes that load the module |
When true, the heartbeat tick also interrupts a locally-running job whose row another replica has already driven to a terminal status (the deadline sweep's TIMEOUT, the orphan sweep's ORPHANED). |
app.agent-jobs.max-concurrent |
${AGENT_JOBS_MAX_CONCURRENT:4} |
all shapes | Size of the bounded worker pool - see AgentJobExecutor for why the bound IS the feature. |
app.agent-jobs.max-concurrent-per-tenant |
2 |
all shapes | Ceiling on how many of those workers ONE tenant may occupy, so a single tenant submitting twenty jobs cannot starve every other tenant on the replica. |
app.agent-jobs.max-timeout |
60m |
all shapes | Clamped to, never rejected against: a client asking for longer than the platform allows is asking for "as long as you'll give me". |
app.agent-jobs.max-turns |
1 |
all shapes | Group app.agent-jobs: The app.agent-jobs.* block (see application.yml). (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/job/AgentJobProperties.java) |
app.agent-jobs.output-schema |
shapes that load the module | Read by the OBSERVABILITY half (structured job output). | |
app.agent-jobs.ownership |
shapes that load the module | Cross-replica job OWNERSHIP LEASE + adoption (core/agents job/ownership). | |
app.agent-jobs.poll-interval |
10s |
all shapes | Group app.agent-jobs: The app.agent-jobs.* block (see application.yml). (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/job/AgentJobProperties.java) |
app.agent-jobs.progress |
shapes that load the module | Read by the OBSERVABILITY half (progress notes + the report_progress tool). | |
app.agent-jobs.queue-capacity |
8 |
all shapes | Hand-off queue depth in front of the pool. |
app.agent-jobs.queue-ttl |
1h |
all shapes | A job left QUEUED longer than this is FAILED rather than run. |
app.agent-jobs.durability.requeue-zero-work-orphans |
true |
shapes that load the module | Whether an orphan that provably did ZERO chargeable work (no turns used, no tool calls logged for its session) may be re-queued instead of failed. |
app.agent-jobs.schedule |
shapes that load the module | Read by the SCHEDULES half. | |
app.agent-jobs.durability.step-journal-enabled |
${AGENT_JOB_STEP_JOURNAL_ENABLED:true} |
shapes that load the module | L1 step journal master switch (default ON since 2026-10-23; resume, rerun and ownership stay OFF): job turns, DURABLE tool effects and delegate_parallel branches are journaled in Postgres (agent_step_journal, V376). |
app.agent-jobs.trigger |
shapes that load the module | Read by the EVENT-TRIGGERS half. | |
app.agent-lab.free-plan-enabled |
${AGENT_LAB_FREE_PLAN_ENABLED:true} |
shapes that load the module | Same ungating for Agent Lab knockout/consistency runs (stored-FREE tenants get AgentLabRunService's FREE_* caps - 12 cells / 24 calls / 4 prompts - instead of a 403). |
app.agent.share.max-chats-per-minute-per-ip |
12 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/share/AgentShareService.java |
app.agent.share.max-vision-sessions-per-minute-per-ip |
6 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/share/AgentShareService.java |
app.agent.share.max-voice-sessions-per-minute-per-ip |
4 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/share/AgentShareService.java |
app.agent.share.max-voice-turns-per-minute |
20 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/share/AgentShareService.java |
app.agents.default-temperature |
0.7 |
all shapes | see source: core/agents/src/main/java/com/agenticai/core/agents/core/AgentDefaults.java |
app.agents.default-top-p |
1.0 |
all shapes | see source: core/agents/src/main/java/com/agenticai/core/agents/core/AgentDefaults.java |
app.app-builder.max-tokens |
8000 |
all shapes | Mirrors app.agents.default-max-tokens' role for AssistService, but app-builder writes whole files (potentially several per turn), so the default ceiling is meaningfully higher. |
app.app-builder.service-token |
${APP_BUILDER_CORE_SERVICE_TOKEN:} |
all shapes | App Builder generation routing fix (2026-08-06, see AppBuilderCompletionController's javadoc). |
app.attestation.issuer |
shapes that load the module | see source: core/agents/src/main/java/com/agenticai/core/agents/attestation/AgentAttestationService.java |
|
app.attestation.ttl-seconds |
300 |
shapes that load the module | see source: core/agents/src/main/java/com/agenticai/core/agents/attestation/AgentAttestationService.java |
app.audio.generation.elevenlabs.enabled |
true |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/ElevenLabsGenerationQuotaService.java |
app.audio.generation.elevenlabs.free-daily-cap |
10 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/ElevenLabsGenerationQuotaService.java |
app.audio.generation.elevenlabs.free-monthly-cap |
100 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/ElevenLabsGenerationQuotaService.java |
app.audio.generation.elevenlabs.paid-daily-cap |
100 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/ElevenLabsGenerationQuotaService.java |
app.audio.generation.elevenlabs.paid-monthly-cap |
1500 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/ElevenLabsGenerationQuotaService.java |
app.audio.generation.enabled |
true |
shapes that load the module | see source: core/sarvam/src/main/java/com/agenticai/core/sarvam/agenttools/AudioGenerationQuotaService.java |
app.audio.generation.free-daily-cap |
20 |
shapes that load the module | see source: core/sarvam/src/main/java/com/agenticai/core/sarvam/agenttools/AudioGenerationQuotaService.java |
app.audio.generation.free-monthly-cap |
200 |
shapes that load the module | see source: core/sarvam/src/main/java/com/agenticai/core/sarvam/agenttools/AudioGenerationQuotaService.java |
app.audio.generation.paid-daily-cap |
200 |
shapes that load the module | see source: core/sarvam/src/main/java/com/agenticai/core/sarvam/agenttools/AudioGenerationQuotaService.java |
app.audio.generation.paid-monthly-cap |
3000 |
shapes that load the module | see source: core/sarvam/src/main/java/com/agenticai/core/sarvam/agenttools/AudioGenerationQuotaService.java |
app.audio.generation.platform-sarvam-key |
shapes that load the module | see source: core/sarvam/src/main/java/com/agenticai/core/sarvam/agenttools/PlatformSarvamSpeakCapabilityProvider.java |
|
app.browser.agent-allowed-domains |
${BROWSER_AGENT_ALLOWED_DOMAINS:} |
all shapes | Per-agent overrides: =host,host;=host (same shape as app.tools.http-request.agent-allowed-hosts) |
app.browser.agent-blocked-domains |
${BROWSER_AGENT_BLOCKED_DOMAINS:} |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseV2Settings.java) |
app.browser.allowed-domains |
${BROWSER_ALLOWED_DOMAINS:} |
all shapes | Comma-separated hosts: exact (example.com) or subdomain wildcard (*.example.com - NOT the apex itself). |
app.browser.blocked-domains |
${BROWSER_BLOCKED_DOMAINS:} |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseV2Settings.java) |
app.browser.profiles.default-ttl-days |
30 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserProfileService.java) |
app.browser.profiles.enabled |
false |
all shapes | Encrypted (AES-256-GCM, APP_ENCRYPTION_KEY) Playwright storageState per profile, loaded into a fresh context and refreshed after each browser action that changed it (checkpoint) and again when the session ends. |
app.browser.profiles.max-per-tenant |
20 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserProfileService.java) |
app.browser.profiles.max-state-bytes |
262144 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserProfileService.java) |
app.browser.profiles.max-ttl-days |
365 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserProfileService.java) |
app.browser.profiles.restrict-navigation |
true |
all shapes | Refuse top-level navigation outside a profile's allowed domains while it is attached. |
app.browser.recipes.enabled |
false |
all shapes | run_browser_recipe + "save this run as a recipe". |
app.browser.recipes.max-per-tenant |
50 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserRecipeService.java) |
app.browser.recipes.max-replay-seconds |
120 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserRecipeService.java) |
app.browser.recipes.step-timeout-ms |
15000 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserRecipeService.java) |
app.browser.secret-domains |
${BROWSER_SECRET_DOMAINS:} |
all shapes | Vault secret -> domain binding for {{secret.NAME}} in browser_type_into: NAME=host,host;NAME2=host. |
app.browser.secret-name-prefix |
${BROWSER_SECRET_NAME_PREFIX:BROWSER_} |
all shapes | profiles / TOTP / recipes (browser persistence and replay; see BrowserProfileService, BrowserTotpToolCallback, BrowserRecipeService) --- Vault secrets that browser_totp and recipe {{secret.X}} / {{totp.X}}... |
app.browser.takeover.enabled |
false |
all shapes | Human takeover: the agent asks (browser_request_user) or the person pauses, then the person sees a live JPEG view of the SAME headless page and drives it (click / key / type / scroll relayed server-side) while every... |
app.browser.takeover.idle-action |
return |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.idle-timeout-seconds |
300 |
all shapes | Seconds without a person's input in USER_CONTROL before control is returned (idle-action=return) or the session is ended (idle-action=expire). |
app.browser.takeover.input.burst |
40 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.input.max-scroll-px |
2000 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.input.max-type-chars |
256 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.input.per-second |
20 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.max-duration-seconds |
900 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.max-streams |
4 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.paused-wait-seconds |
600 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.reason-max-chars |
200 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.require-explicit-allowlist |
true |
all shapes | true = takeover only on sites an explicit allow-list covers (app.browser.allowed-domains, an agent allow-list, or the attached browser profile's domains). |
app.browser.takeover.resume-text-max-chars |
1500 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.stream.fps |
2 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.stream.jpeg-quality |
55 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.stream.max-dimension |
1024 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserTakeoverSettings.java) |
app.browser.takeover.token-secret |
${BROWSER_TAKEOVER_TOKEN_SECRET:} |
all shapes | HMAC key for takeover tokens. |
app.browser.takeover.token-ttl-seconds |
120 |
all shapes | Lifetime of the signed token that opens the stream and authorises input (renewed by the client). |
app.browser.totp.enabled |
false |
all shapes | browser_totp(secretName): current RFC 6238 code from a vault seed; confirmation-gated by default. |
app.browser.v2.enabled |
false |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseV2Settings.java) |
app.browser.v2.legacy-click-type-enabled |
false |
all shapes | Keep the legacy browser_click / browser_type on the tool list next to the v2 tools (only matters when v2 is on). |
app.browser.v2.max-elements |
120 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseV2Settings.java) |
app.browser.v2.name-max-chars |
80 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseV2Settings.java) |
app.browser.v2.strict-staleness |
false |
all shapes | true = ANY structural DOM change makes snapshot indexes stale (safest; unusable on constantly mutating pages). |
app.browser.v2.stuck-abort-after |
8 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseV2Settings.java) |
app.browser.v2.stuck-warn-after |
4 |
all shapes | Consecutive no-effect actions before a "you appear stuck" observation / before the task is aborted. |
app.browser.v2.text-digest-chars |
400 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseV2Settings.java) |
app.browser.v2.value-max-chars |
40 |
all shapes | Group app.browser: Browser agent (core/agents-browser tools/browseruse). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseV2Settings.java) |
app.browseruse.action-timeout-ms |
15000 |
shapes that load the module | see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseSessionManager.java |
app.browseruse.chromium-executable-path |
shapes that load the module | see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseSessionManager.java |
|
app.browseruse.enabled |
auto |
shapes that load the module | see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseSessionManager.java |
app.browseruse.max-sessions-per-tenant |
2 |
shapes that load the module | see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseSessionManager.java |
app.browseruse.max-total-sessions |
10 |
shapes that load the module | see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseSessionManager.java |
app.browseruse.navigation-timeout-ms |
20000 |
shapes that load the module | see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseSessionManager.java |
app.browseruse.session-idle-timeout-seconds |
300 |
shapes that load the module | see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseSessionManager.java |
app.browseruse.session-max-lifetime-seconds |
1800 |
shapes that load the module | see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/browseruse/BrowserUseSessionManager.java |
app.chat-export.bulk-max-chars |
20000000 |
all shapes | "Export all imported chats" stops once this many message characters are in the file. |
app.chat-export.enabled |
false |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatExportService.java |
app.chat-import.enabled |
false |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatExportService.java |
app.chat-import.free-max-stored-bytes |
26214400 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.free-max-stored-chats |
200 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-bytes |
26214400 |
all shapes | Largest accepted upload (bytes) and the caps that keep a hostile or enormous export bounded. |
app.chat-import.max-chars-per-chat |
500000 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-chars-per-import |
20000000 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-chars-per-message |
50000 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-conversations-per-import |
500 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-mapping-nodes |
20000 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-messages-per-chat |
2000 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-scan-conversations |
5000 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-stored-bytes |
262144000 |
all shapes | Storage one user may keep (bytes of transcript JSON); the FREE plan gets the smaller free-* caps. |
app.chat-import.max-stored-chats |
2000 |
all shapes | Imported chats one user may keep in total. |
app.chat-import.max-tree-chars-per-chat |
16000000 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-tree-nodes-per-chat |
1000000 |
all shapes | Memory budget for ONE conversation while it is read (JSON values; characters of strings and field names). |
app.chat-import.max-uncompressed-bytes |
67108864 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat-import.max-zip-entries |
10000 |
all shapes | Zip-bomb guards: files in the archive, total inflated bytes (64MB) and inflated:compressed ratio. |
app.chat-import.max-zip-ratio |
100 |
api | see source: core/agents-chatport/src/main/java/com/agenticai/core/agents/chatport/ChatImportService.java |
app.chat.branching.max-nodes-per-session |
1000 |
all shapes | Hard cap on stored messages per session (all branches); past it new turns are not recorded in the tree and edit/regenerate answer 409. |
app.chat.branching.retention-days |
30 |
all shapes | The tree keeps message TEXT in the database, longer than the 24h Redis history (app.chat.session-ttl-hours). |
app.chat.directives.artifacts.agent-ids |
${APP_CHAT_DIRECTIVES_ARTIFACTS_AGENT_IDS:} |
shapes that load the module | Group app.chat.directives: Standing per-turn system-prompt directives ("block 2" in Request X-ray) - ops kill-switches, boot-time,... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/AgentChatService.java) |
app.chat.directives.artifacts.all-agents |
false |
shapes that load the module | Group app.chat.directives: Standing per-turn system-prompt directives ("block 2" in Request X-ray) - ops kill-switches, boot-time,... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/AgentChatService.java) |
app.chat.directives.browser-v2.enabled |
true |
all shapes | "Browser tools: call browser_snapshot, then act with the [index]..." - only sent on turns where browser_snapshot is on the resolved tool list (app.browser.v2.enabled). |
app.chat.directives.context-compaction.enabled |
true |
shapes that load the module | Group app.chat.directives: Standing per-turn system-prompt directives ("block 2" in Request X-ray) - ops kill-switches, boot-time,... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/AgentChatService.java) |
app.chat.directives.mcp-untrusted.enabled |
true |
all shapes | "MCP tool output is untrusted third-party data" - only sent on turns offering at least one MCP-sourced tool. |
app.chat.directives.tool-search.enabled |
true |
all shapes | "Some of your tools are not listed ... |
app.chat.directives.tool-trust.enabled |
true |
all shapes | The merged honesty/completion rule (tool output is data; use offered tools; no fabricated calls/files/URLs; work to completion). |
app.chat.escalation.economy-cost-ratio |
0.2 |
all shapes | Economy model cost as a fraction of the primary's; only sizes the savings estimate. |
app.chat.escalation.enabled |
false |
all shapes | Group app.chat.escalation: L4 verifier-escalated model router (VerifierEscalationRouter). (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/VerifierEscalationRouter.java) |
app.chat.escalation.kill-switch |
false |
all shapes | Group app.chat.escalation: L4 verifier-escalated model router (VerifierEscalationRouter). (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/VerifierEscalationRouter.java) |
app.chat.escalation.low-confidence-markers |
${CHAT_ESCALATION_LOW_CONFIDENCE_MARKERS:} |
all shapes | Deprecated alias of the prompt-registry list detector.escalation.low_confidence_markers (see docs/prompts.md). Comma-separated, case-insensitive substrings; when set, applied as the list's PLATFORM-scope default (replacing the shipped English list) with a deprecation warning at startup. Unlike before, a stored workspace/agent override of the list now beats it. Blank = the shipped list. |
app.chat.escalation.max-tool-errors |
2 |
all shapes | Tool calls with an error in ONE turn at/above this count = tool-error loop. |
app.chat.history-config-cache-ms |
10000 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionMemoryService.java |
app.chat.history-max-chars |
32000 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionMemoryService.java |
app.chat.history-max-tokens |
8000 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionMemoryService.java |
app.chat.history-max-turns |
40 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionMemoryService.java |
app.chat.history-summary-enabled |
true |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionSummaryService.java |
app.chat.history-summary-max-chars |
4000 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionSummaryService.java |
app.chat.history-summary-max-prompt-chars |
24000 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionSummaryService.java |
app.chat.history-summary-max-tokens |
600 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionSummaryService.java |
app.chat.history-summary-turn-excerpt-chars |
800 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/memory/SessionSummaryService.java |
app.chat.hotpath.async-attachment-fetch |
false |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/ChatHotpathConfig.java |
app.chat.hotpath.client-cache-providers |
ANTHROPIC |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/ChatHotpathConfig.java |
app.chat.hotpath.client-cache-ttl |
0s |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/ChatHotpathConfig.java |
app.chat.hotpath.credential-cache-ttl |
0s |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/ChatHotpathConfig.java |
app.chat.hotpath.rl-batch-select |
false |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/ChatHotpathConfig.java |
app.chat.response-cache-ttl-hours |
6 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/cache/LlmResponseCacheService.java |
app.chat.resumable-streams.buffer-max-events |
5000 |
all shapes | Events kept per turn (one event = one streamed chunk). |
app.chat.resumable-streams.detach-timeout-seconds |
900 |
all shapes | Cost guard: a generation with NO reader attached for this long is cancelled (partial reply persisted as for any cancel). |
app.chat.resumable-streams.enabled |
false |
all shapes | Group app.chat.resumable-streams: Resumable chat streams (ResumableChatStreamService): a long answer survives a page refresh, a sleeping... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/resumable/ResumableChatStreamService.java) |
app.chat.resumable-streams.max-bytes-per-turn |
4194304 |
all shapes | Heap guards for the in-memory buffer (estimates: 2 bytes per char plus overhead). |
app.chat.resumable-streams.max-readers-per-user |
8 |
all shapes | Reconnect-storm cap: concurrent resume readers per tenant+user (0 = unlimited); the 9th gets 429 and backs off. |
app.chat.resumable-streams.max-total-bytes |
268435456 |
all shapes | Group app.chat.resumable-streams: Resumable chat streams (ResumableChatStreamService): a long answer survives a page refresh, a sleeping... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/resumable/ResumableChatStreamService.java) |
app.chat.resumable-streams.store |
memory |
all shapes | memory (default, per JVM: a resume must reach the replica that is generating the turn; fine for one replica) or redis (entries are also mirrored to Redis, tenant-first keys, so any replica can serve a resume; Redis... |
app.chat.resumable-streams.ttl-seconds |
600 |
all shapes | How long a finished turn stays resumable. |
app.chat.semantic-cache.max-entries-per-agent |
200 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/cache/SemanticResponseCacheService.java |
app.chat.semantic-cache.similarity-threshold |
0.95 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/cache/SemanticResponseCacheService.java |
app.chat.semantic-cache.ttl-hours |
6 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/cache/SemanticResponseCacheService.java |
app.chat.sse.buffered-keepalive-interval |
20s |
all shapes | How often a BUFFERED-depth chat turn (CAREFUL/RIGOROUS/PLANNED) writes an empty SSE chunk while it works - 2026-09-17 reasoning-depth audit. |
app.chat.sse.memory-events.enabled |
true |
all shapes | One "memory used" step event per step-event turn; read through a supplier by ChatMemoryRecall. |
app.chat.sse.step-heartbeat-interval |
15s |
all shapes | How often a STEP-EVENT turn (ChatRequest.stepEvents=true - the web console's opt-in activity trace) writes a {"type":"heartbeat"} step event while the turn is open - 2026-09-25 latency audit. |
app.coding-harness.approval-timeout-seconds |
900 |
all shapes | How long the harness waits for the human decision before discarding the change (fail closed), and how long the sandbox stays up after approval so the user can still roll back. |
app.coding-harness.default-steps |
20 |
all shapes | Budget a request may ask for is clamped to the max-* values; omitted = default-*. |
app.coding-harness.default-tokens |
150000 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.default-wall-seconds |
900 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.free-plan-enabled |
false |
all shapes | Paid plans only unless this is true (stored plan, like the Agent Lab gate). |
app.coding-harness.git-allowed-hosts |
${CODING_HARNESS_GIT_ALLOWED_HOSTS:} |
all shapes | Optional allow-list of git hosts (exact or *.suffix); empty = any PUBLIC https host (SSRF-guarded). |
app.coding-harness.max-archive-bytes |
26214400 |
all shapes | Uploaded archive cap (bytes). |
app.coding-harness.max-fix-iterations |
3 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.max-fix-iterations-cap |
5 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.max-instruction-chars |
4000 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.max-output-chars |
8000 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.max-steps |
40 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.max-tokens |
400000 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.max-wall-seconds |
1800 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.rollback-window-seconds |
600 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.runner |
e2b |
all shapes | e2b (default) or local. |
app.coding-harness.test-timeout-seconds |
300 |
all shapes | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/coding/CodingTaskSettings.java |
app.coding-harness.tool-wait-seconds |
240 |
all shapes | How long the coding_task tool waits in one chat turn before handing back a task id. |
app.context.compaction.clear-at |
0.6 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/ChatContextCompaction.java) |
app.context.compaction.compact-at |
0.85 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/ChatContextCompaction.java) |
app.context.compaction.default-window-tokens |
128000 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/ChatContextCompaction.java) |
app.context.compaction.keep-recent-messages |
6 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/ChatContextCompaction.java) |
app.context.compaction.keep-recent-tool-results |
3 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/ChatContextCompaction.java) |
app.context.compaction.max-summaries-per-run |
3 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/ChatContextCompaction.java) |
app.context.compaction.restore-tool-enabled |
true |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/ChatContextCompaction.java) |
app.context.compaction.summary-input-chars |
24000 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/LlmCompactionSummarizer.java) |
app.context.compaction.summary-max-tokens |
800 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/ChatContextCompaction.java) |
app.context.compaction.summary-timeout-ms |
20000 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/LlmCompactionSummarizer.java) |
app.context.compaction.summary-turn-excerpt-chars |
2000 |
all shapes | Group app.context: Context compaction for long-running agents (deep research, browser tasks, coding) - see... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/context/LlmCompactionSummarizer.java) |
app.data-tables.enabled |
false |
all shapes | Group app.data-tables: Data Tables: tenant-scoped spreadsheet-like tables that workflows (the data_table node) and agents... (see source: core/agents-tools/src/main/java/com/agenticai/core/tables/DataTableService.java) |
app.data-tables.free-plan-enabled |
true |
all shapes | Group app.data-tables: Data Tables: tenant-scoped spreadsheet-like tables that workflows (the data_table node) and agents... (see source: core/agents-tools/src/main/java/com/agenticai/core/tables/DataTableService.java) |
app.data-tables.max-cell-bytes |
65536 |
all shapes | Group app.data-tables: Data Tables: tenant-scoped spreadsheet-like tables that workflows (the data_table node) and agents... (see source: core/agents-tools/src/main/java/com/agenticai/core/tables/DataTableService.java) |
app.data-tables.max-columns |
50 |
all shapes | Group app.data-tables: Data Tables: tenant-scoped spreadsheet-like tables that workflows (the data_table node) and agents... (see source: core/agents-tools/src/main/java/com/agenticai/core/tables/DataTableService.java) |
app.data-tables.max-import-bytes |
1048576 |
all shapes | 1 MB matches Spring's default multipart file limit; raising this alone does nothing for uploads - spring.servlet.multipart.max-file-size has to rise with it. |
app.data-tables.max-import-rows |
10000 |
all shapes | Group app.data-tables: Data Tables: tenant-scoped spreadsheet-like tables that workflows (the data_table node) and agents... (see source: core/agents-tools/src/main/java/com/agenticai/core/tables/DataTableService.java) |
app.data-tables.max-rows-per-table |
100000 |
all shapes | Group app.data-tables: Data Tables: tenant-scoped spreadsheet-like tables that workflows (the data_table node) and agents... (see source: core/agents-tools/src/main/java/com/agenticai/core/tables/DataTableService.java) |
app.data-tables.max-tables |
50 |
all shapes | Group app.data-tables: Data Tables: tenant-scoped spreadsheet-like tables that workflows (the data_table node) and agents... (see source: core/agents-tools/src/main/java/com/agenticai/core/tables/DataTableService.java) |
app.decision.policy-cache-ttl |
30s |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/decision/DecisionEngine.java |
app.delegation.api-audience |
vegaduta-api |
all shapes | Audience value a delegation token must carry to be accepted as an API credential (DelegationAuthenticatorAdapter). |
app.delegation.max-ttl-seconds |
3600 |
all shapes | Hard ceiling on any single token's life AND the per-grant ttl cap - delegation is for scoped, short-lived authority, not standing access. |
app.demo.studio.approval-store |
memory |
all shapes | memory (default, today) or redis; field-injected so the test-built constructor keeps the default. |
app.demo.studio.cooldown-minutes |
10 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioFairUse.java |
app.demo.studio.extracted-doc-types-enabled |
false |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioDocumentService.java |
app.demo.studio.ip-daily-ceiling |
600 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioFairUse.java |
app.demo.studio.ip-missions-per-minute |
15 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioFairUse.java |
app.demo.studio.max-agents-per-ip |
40 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioService.java |
app.demo.studio.max-agents-per-session |
12 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioService.java |
app.demo.studio.max-concurrent-runs |
2 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioFairUse.java |
app.demo.studio.max-connectors-per-agent |
3 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioConnectorService.java |
app.demo.studio.max-doc-bytes |
409600 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioDocumentService.java |
app.demo.studio.max-docs-per-agent |
5 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioDocumentService.java |
app.demo.studio.mcp-connectors-enabled |
false |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioConnectorService.java |
app.demo.studio.mission-timeout-seconds |
120 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioService.java |
app.demo.studio.missions-per-minute |
6 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioFairUse.java |
app.demo.studio.moderation.cache-max-entries |
2000 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioModerationService.java |
app.demo.studio.moderation.cache-ttl-minutes |
10 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioModerationService.java |
app.demo.studio.moderation.enabled |
true |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioModerationService.java |
app.demo.studio.moderation.llm-enabled |
true |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioModerationService.java |
app.demo.studio.moderation.llm-timeout-ms |
4000 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioModerationService.java |
app.demo.studio.moderation.terms-file |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioModerationService.java |
|
app.demo.studio.strike-limit |
3 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioFairUse.java |
app.demo.studio.strike-window-minutes |
10 |
shapes that load the module | see source: core/agents-studio/src/main/java/com/agenticai/core/agents/demo/studio/DemoStudioFairUse.java |
app.edge.turns.rate-limit.max-events-per-ip |
300 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/edge/EdgeTurnTelemetryRateLimiter.java |
app.edge.turns.rate-limit.window-minutes |
60 |
shapes that load the module | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/edge/EdgeTurnTelemetryRateLimiter.java |
app.free-tier.quota-config-cache-ms |
10000 |
shapes that load the module | 2026-08-08 usage-counter audit fix: checkAndReserveTokenBudget's reservation amount is maxTokens (a real, provider-enforced ceiling on the completion side) PLUS this fixed estimate for the prompt side, which has no... |
app.free-tier.token-reservation-prompt-estimate |
2000 |
shapes that load the module | 2026-08-08 usage-counter audit fix: checkAndReserveTokenBudget's reservation amount is maxTokens (a real, provider-enforced ceiling on the completion side) PLUS this fixed estimate for the prompt side, which has no... |
app.graph-memory.user-forget-sync.enabled |
false |
all shapes | When a user edits or deletes a fact on /api/memory, also invalidate (valid_to stamp, no DELETE) the graph edges that fact's text supported - see GraphMemoryService.forgetFactText. |
app.guardrails.run-budget.avg-branch-tokens |
8000 |
all shapes | Group app.guardrails.run-budget: Per-run USD cap for delegation tools (ask_agent_*, delegate_parallel). (see source: core/agents/src/main/java/com/agenticai/core/agents/runbudget/AgentRunBudgetService.java) |
app.guardrails.run-budget.input-usd-per-m |
3.00 |
all shapes | Group app.guardrails.run-budget: Per-run USD cap for delegation tools (ask_agent_*, delegate_parallel). (see source: core/agents/src/main/java/com/agenticai/core/agents/runbudget/AgentRunBudgetService.java) |
app.guardrails.run-budget.max-per-agent-usd |
10 |
all shapes | Ceiling for an agent's own cap, used ONLY when usd above is 0 (no platform cap). |
app.guardrails.run-budget.output-usd-per-m |
15.00 |
all shapes | Group app.guardrails.run-budget: Per-run USD cap for delegation tools (ask_agent_*, delegate_parallel). (see source: core/agents/src/main/java/com/agenticai/core/agents/runbudget/AgentRunBudgetService.java) |
app.guardrails.run-budget.per-agent-override-enabled |
true |
all shapes | Per-agent cap (agent.run_budget_usd, V421; GET/PATCH /api/agents/{id}/run-budget). |
app.hosted-pool.circuit.cooldown-seconds |
180 |
all shapes | see source: core/agents/src/main/java/com/agenticai/core/agents/hosted/HostedProviderHealth.java |
app.hosted-pool.circuit.failure-threshold |
3 |
all shapes | Deliberately tighter than tool-circuit-breaker above: a hosted pool row is shared platform-wide (not per tenant, per tool), so the evidence accumulates far faster and the cost of leaving a dead row at the head of the... |
app.hosted-pool.circuit.failure-window-seconds |
120 |
all shapes | see source: core/agents/src/main/java/com/agenticai/core/agents/hosted/HostedProviderHealth.java |
app.hosted-pool.order-by-cost |
true |
all shapes | Escape hatch for an operator who wants the chain tried in exactly the order they typed into Platform Admin: false = pure priority ASC, ignoring cost and latency entirely (the pre-hardening behaviour). |
app.hosted-pool.reasoning-config-cache-ms |
10000 |
shapes that load the module | Same 10s time-box FreeTierUsageLimiterService's quota-config cache uses, for the same reason: the effort is read on every chain build (the chat hot path), an uncached read would add a Postgres round trip per message. |
app.jev.agent-routing.enabled |
true |
all shapes | Quick Task agent selection (AgentRelevanceSelector): Jev choice for the primary pick, one batched noul call for additional relevant agents. |
app.jev.base-url |
${APP_JEV_BASE_URL:https://api.typesafe.ai} |
all shapes | Group app.jev: Jev - TypeSafe AI's System One decision model (JevDecisionService, core/agents/decision). (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/decision/JevCredentialValidator.java) |
app.jev.complexity.enabled |
true |
all shapes | Cost-routing second opinion (QueryComplexityClassifier): only a heuristic "not simple" is offered to Jev - a heuristic "simple" keeps its zero-latency path. |
app.jev.complexity.simple-threshold |
0.75 |
all shapes | Group app.jev: Jev - TypeSafe AI's System One decision model (JevDecisionService, core/agents/decision). (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/quality/QueryComplexityClassifier.java) |
app.jev.connect-timeout |
2s |
all shapes | Tight by design: a decision model slower than this is worth less than the heuristic it replaces, and Jev sits in front of real user turns. |
app.jev.enabled |
true |
all shapes | Group app.jev: Jev - TypeSafe AI's System One decision model (JevDecisionService, core/agents/decision). (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/decision/JevDecisionService.java) |
app.jev.key-cache-ttl |
30s |
all shapes | Bounds how long an admin's credential rotate/remove takes to reach every pod - the resolved key (including "none") is cached this long. |
app.jev.model |
${APP_JEV_MODEL:jev-latest} |
all shapes | Group app.jev: Jev - TypeSafe AI's System One decision model (JevDecisionService, core/agents/decision). (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/decision/JevCredentialValidator.java) |
app.jev.read-timeout |
3s |
all shapes | Group app.jev: Jev - TypeSafe AI's System One decision model (JevDecisionService, core/agents/decision). (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/decision/JevDecisionService.java) |
app.knowledge.query-rewrite.allow-byok-tenants |
false |
all shapes | Group app.knowledge.query-rewrite: query-rewrite: ONE bounded cheap-model call per search (multi = alternative queries, hyde = one... (see source: core/agents-studio/src/main/java/com/agenticai/core/agents/ragquality/PlatformQueryRewriteModel.java) |
app.llm.judge.max-excerpt-length |
600 |
all shapes | see source: core/agents-turn/src/main/java/com/agenticai/core/agents/turn/quality/LlmQualityJudgeService.java |
app.llm.judge.sample-rate |
0.05 |
all shapes | Gap 2 (Reliability/Governance/SDLC build plan) - LlmQualityJudgeService. |
app.mcp.apps.allow-https-images |
true |
all shapes | img-src https: (the default CSP allows remote images; set false to restrict to data: images only). |
app.mcp.apps.allowed-domains |
${APP_MCP_APPS_ALLOWED_DOMAINS:} |
all shapes | Comma-separated https hosts (or *.suffix) a server's _meta.ui.csp may widen the CSP to. |
app.mcp.apps.cache-ttl-seconds |
60 |
all shapes | In-memory cache of fetched HTML, keyed by (registration, uri). |
app.mcp.apps.calls-per-minute |
30 |
all shapes | tools/call requests per user per minute that an app may make (each still goes through approvals etc.). |
app.mcp.apps.fetch-timeout-ms |
5000 |
all shapes | Bound on one resources/read; a per-tenant cap on concurrent fetches also applies. |
app.mcp.apps.max-concurrent-fetches-per-tenant |
4 |
all shapes | Group app.mcp.apps: MCP Apps (ui:// HTML resources attached to MCP tools; McpAppsService/McpAppsController). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/mcpapps/McpAppsService.java) |
app.mcp.apps.max-resource-bytes |
524288 |
all shapes | Hard ceiling on the app document (UTF-8 bytes); larger apps are refused, not truncated. |
app.mcp.apps.max-result-chars |
65536 |
all shapes | Group app.mcp.apps: MCP Apps (ui:// HTML resources attached to MCP tools; McpAppsService/McpAppsController). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/mcpapps/McpAppsService.java) |
app.mcp.apps.max-sessions |
500 |
all shapes | Group app.mcp.apps: MCP Apps (ui:// HTML resources attached to MCP tools; McpAppsService/McpAppsController). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/mcpapps/McpAppsService.java) |
app.mcp.apps.session-ttl-seconds |
900 |
all shapes | Lifetime of the per-tool-result handle the browser holds, and how many are kept (oldest evicted). |
app.mcp.client-initialization-timeout-seconds |
120 |
shapes that load the module | see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/McpClientTimeoutConfig.java |
app.mcp.registry-command-timeout |
PT5S |
shapes that load the module | see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/mcpregistry/McpOwnershipRegistryConfiguration.java |
app.mcp.registry-exclusive-clients |
false |
shapes that load the module | see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/mcpregistry/McpOwnershipRegistryConfiguration.java |
app.mcp.registry-lease-ttl |
PT60S |
shapes that load the module | see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/mcpregistry/McpOwnershipRegistryConfiguration.java |
app.mcp.static.shared-stateful-enabled |
false |
profile mcp, shapes that load the module |
see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/StaticMcpExposurePolicy.java |
app.mcp.static.stateless-servers |
sequential-thinking |
profile mcp, shapes that load the module |
Comma-separated STRING, not a YAML list: @Value cannot bind an indexed YAML list. |
app.mcp.stdio.preflight-fail-fast |
true |
all shapes | see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/McpStdioLauncherPreflight.java |
app.memory.blocks.active-project-enabled |
true |
all shapes | Reserved block 'active_project' (content = a project slug): that project's note renders on every turn without being named, until the user switches. |
app.memory.blocks.max-chars-per-block |
1200 |
all shapes | Hard character cap per block, enforced on write and again when rendering the prompt. |
app.memory.blocks.max-project-blocks |
20 |
all shapes | Max named project notes (project:) per user; these render only when the message names the project. |
app.memory.blocks.max-total-chars |
4000 |
all shapes | Total character budget across ALL blocks rendered into one turn (per-block cap still applies). |
app.memory.import.max-chars |
20000 |
all shapes | Longest paste accepted (characters); beyond it the preview answers 400. |
app.memory.import.max-facts |
200 |
all shapes | Most facts kept from one paste / accepted in one import; extra lines come back as "over_limit". |
app.memory.self-service.enabled |
true |
all shapes | api/memory add-a-fact (POST /facts), forget-everything (DELETE ?confirm=true) and export (GET /export). |
app.memory.self-service.max-facts-per-agent |
25 |
all shapes | A hand-added fact never evicts another user's memory. |
app.memory.self-service.max-shared-facts |
50 |
all shapes | Group app.memory: User-visible, editable memory blocks (gap-analysis item 11) - see MemoryBlocks / UserMemoryController... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/usermemory/UserMemoryFactService.java) |
app.music.generation.enabled |
true |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/MusicGenerationQuotaService.java |
app.music.generation.free-daily-cap |
2 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/MusicGenerationQuotaService.java |
app.music.generation.free-max-seconds |
30 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/MusicGenerationQuotaService.java |
app.music.generation.free-monthly-cap |
10 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/MusicGenerationQuotaService.java |
app.music.generation.paid-daily-cap |
20 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/MusicGenerationQuotaService.java |
app.music.generation.paid-max-seconds |
120 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/MusicGenerationQuotaService.java |
app.music.generation.paid-monthly-cap |
200 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/agenttools/MusicGenerationQuotaService.java |
app.openai-compat.first-token-wait-seconds |
30 |
all shapes | Streaming only: how long to hold the response for the first token so a budget or access failure can still be a real HTTP 429/403. |
app.openai-compat.max-prompt-chars |
16000 |
all shapes | Max characters of the folded prompt (history transcript + last message). |
app.openai-compat.rate-limit.max-requests |
60 |
all shapes | Group app.openai-compat.rate-limit: Fixed-window per-caller request cap (Redis, in-memory per-pod fallback). (see source: core/agents-interop/src/main/java/com/agenticai/core/agents/openai/OpenAiCompatRateLimiter.java) |
app.openai-compat.rate-limit.window-seconds |
60 |
all shapes | Group app.openai-compat.rate-limit: Fixed-window per-caller request cap (Redis, in-memory per-pod fallback). (see source: core/agents-interop/src/main/java/com/agenticai/core/agents/openai/OpenAiCompatRateLimiter.java) |
app.openai-compat.timeout-seconds |
300 |
all shapes | Upper bound on a non-streaming call blocking its request thread. |
app.per-user-budget.config-cache-ms |
10000 |
shapes that load the module | see source: core/agents-budget/src/main/java/com/agenticai/core/agents/budget/PerUserBudgetService.java |
app.platform-llm.premium-multiplier |
2 |
shapes that load the module | 972 - "there should be limit on the tokens ... |
app.platform-llm.unlimited-multiplier |
3 |
shapes that load the module | 972 - "there should be limit on the tokens ... |
app.research.deep.auto-offer-on-intent |
true |
all shapes | Token diet: the two tool schemas are built ONLY for agents that list deep_research/verify_citations in their tool allow-list (explicit opt-in) or, when this is true, when the user's message looks like a research... |
app.research.deep.enabled |
false |
all shapes | Group app.research: Deep research (gap-analysis item 10): the deep_research + verify_citations built-in tools. (see source: core/agents/src/main/java/com/agenticai/core/agents/capability/DeepResearchCapability.java) |
app.research.deep.fetch-chars |
12000 |
all shapes | Group app.research: Deep research (gap-analysis item 10): the deep_research + verify_citations built-in tools. (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.research.deep.max-fetches-per-run |
12 |
all shapes | Page fetch attempts across every deep_research call in one chat turn. |
app.research.deep.max-searches-per-run |
12 |
all shapes | Brave search queries (platform-paid) across every deep_research call in one chat turn. |
app.research.deep.max-searches-per-user-per-day |
60 |
all shapes | Brave search queries one user may spend through deep_research per 24h window, on EVERY path (research runs, chat, direct jobs, any agent with the capability). |
app.research.deep.max-sources |
8 |
all shapes | Group app.research: Deep research (gap-analysis item 10): the deep_research + verify_citations built-in tools. (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/research/ResearchRunProperties.java) |
app.research.deep.max-workers |
6 |
all shapes | Group app.research: Deep research (gap-analysis item 10): the deep_research + verify_citations built-in tools. (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.research.deep.min-quote-chars |
20 |
all shapes | verify_citations rejects quotes shorter than this (half for CJK) or with < 3 distinct content terms. |
app.research.deep.passages-per-source |
2 |
all shapes | Group app.research: Deep research (gap-analysis item 10): the deep_research + verify_citations built-in tools. (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.research.deep.results-per-search |
6 |
all shapes | Group app.research: Deep research (gap-analysis item 10): the deep_research + verify_citations built-in tools. (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.research.deep.total-deadline-seconds |
120 |
all shapes | Overall wall-clock cap for ONE deep_research call (worker timeouts alone stack to ~10 min). |
app.research.deep.worker-timeout-seconds |
45 |
all shapes | Group app.research: Deep research (gap-analysis item 10): the deep_research + verify_citations built-in tools. (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.research.runs.enabled |
false |
all shapes | Group app.research.runs: Research runs: the /api/research facade over agent jobs (one run = one agent_job row on the caller's own... (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/research/ResearchRunProperties.java) |
app.research.runs.max-per-user-per-day |
5 |
all shapes | Runs one user may start per 24h window (Redis counter, fail-open). |
app.research.runs.timeout-seconds |
600 |
all shapes | Job timeout requested for a run (clamped by app.agent-jobs.max-timeout). |
app.sandbox.data-analysis.artifact-ttl-hours |
24 |
all shapes | Returned charts/workbooks/scripts are deleted from object storage after this many hours. |
app.sandbox.data-analysis.enabled |
false |
all shapes | Group app.sandbox: Data-analysis sandbox (gap-analysis item 9). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/e2b/SandboxArtifactRetention.java) |
app.sandbox.data-analysis.max-file-bytes |
10485760 |
all shapes | Group app.sandbox: Data-analysis sandbox (gap-analysis item 9). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/e2b/SandboxFileRegistry.java) |
app.sandbox.data-analysis.max-files |
6 |
all shapes | Group app.sandbox: Data-analysis sandbox (gap-analysis item 9). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/e2b/SandboxFileRegistry.java) |
app.sandbox.data-analysis.max-session-bytes |
20971520 |
all shapes | Group app.sandbox: Data-analysis sandbox (gap-analysis item 9). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/e2b/SandboxFileRegistry.java) |
app.sandbox.data-analysis.max-sessions |
50 |
all shapes | Bounds on the in-memory file registry. |
app.sandbox.data-analysis.max-sessions-per-tenant |
10 |
all shapes | Group app.sandbox: Data-analysis sandbox (gap-analysis item 9). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/e2b/SandboxFileRegistry.java) |
app.sandbox.data-analysis.max-tenant-bytes |
50331648 |
all shapes | Group app.sandbox: Data-analysis sandbox (gap-analysis item 9). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/e2b/SandboxFileRegistry.java) |
app.sandbox.data-analysis.max-total-bytes |
134217728 |
all shapes | Group app.sandbox: Data-analysis sandbox (gap-analysis item 9). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/e2b/SandboxFileRegistry.java) |
app.sandbox.data-analysis.startup-sweep-enabled |
true |
all shapes | On boot (only when enabled=true) list stored artifacts across tenants and delete the expired ones / re-register the live ones, so a restart no longer orphans them. |
app.sandbox.data-analysis.store |
memory |
all shapes | memory (default) or redis; field-injected so the test-built constructors keep the default. |
app.sandbox.data-analysis.ttl-minutes |
60 |
all shapes | Group app.sandbox: Data-analysis sandbox (gap-analysis item 9). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/e2b/SandboxFileRegistry.java) |
app.sandbox.platform-e2b.allow-demo-tenant |
false |
all shapes | The shared /try demo tenant never runs on the platform key unless this is true. |
app.sandbox.platform-e2b.free-daily-runs |
20 |
all shapes | Group app.sandbox.platform-e2b: Per-tenant gating (no key): uploads, mounting and the tool all require the tenant to have an ACTIVE E2B... (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/e2b/PlatformSandboxRunQuota.java) |
app.sandbox.platform-e2b.global-daily-runs |
2000 |
all shapes | Platform-wide ceiling across ALL tenants per UTC day; max-timeout-seconds is clamped to 1..120. |
app.sandbox.platform-e2b.max-timeout-seconds |
30 |
all shapes | Group app.sandbox.platform-e2b: Per-tenant gating (no key): uploads, mounting and the tool all require the tenant to have an ACTIVE E2B... (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/e2b/PlatformSandboxRunQuota.java) |
app.sandbox.platform-e2b.paid-daily-runs |
200 |
all shapes | Group app.sandbox.platform-e2b: Per-tenant gating (no key): uploads, mounting and the tool all require the tenant to have an ACTIVE E2B... (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/e2b/PlatformSandboxRunQuota.java) |
app.sandbox.platform-e2b.quota-store |
memory |
all shapes | memory (default) or redis; field-injected so the test-built constructors keep the default. |
app.scheduled-tasks.catch-up-window-minutes |
720 |
all shapes | A run that was due while the service was down still fires (once) if it is at most this old; older ones are recorded as missed. |
app.scheduled-tasks.delivery-channels |
${SCHEDULED_TASKS_DELIVERY_CHANNELS:in_app,email,whatsapp,whatsapp_qr,telegram,slack,signal,matrix,imessage,a2a,voice} |
all shapes | Where a result may be delivered. |
app.scheduled-tasks.enabled |
false |
all shapes | Group app.scheduled-tasks: Scheduled tasks (core/agents scheduledtask) - "every weekday at 8am summarise my unread email and send me... (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/scheduledtask/ScheduledTaskSettings.java) |
app.scheduled-tasks.free-max-per-tenant |
3 |
all shapes | Group app.scheduled-tasks: Scheduled tasks (core/agents scheduledtask) - "every weekday at 8am summarise my unread email and send me... (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/scheduledtask/ScheduledTaskSettings.java) |
app.scheduled-tasks.free-plan-enabled |
true |
all shapes | FREE-plan workspaces may schedule (at the tighter cap below) unless this is false. |
app.scheduled-tasks.max-consecutive-failures |
3 |
all shapes | Failed runs in a row (a run retried once and failed again counts once) before the task pauses itself. |
app.scheduled-tasks.max-delivery-chars |
3500 |
all shapes | Result text is trimmed to this before delivery (a WhatsApp message tops out near 4096). |
app.scheduled-tasks.max-per-tenant |
10 |
all shapes | Tasks per workspace (enabled or paused). |
app.scheduled-tasks.max-prompt-chars |
2000 |
all shapes | Group app.scheduled-tasks: Scheduled tasks (core/agents scheduledtask) - "every weekday at 8am summarise my unread email and send me... (see source: core/agents-jobs/src/main/java/com/agenticai/core/agents/scheduledtask/ScheduledTaskSettings.java) |
app.scheduled-tasks.min-interval-minutes |
15 |
all shapes | A schedule must not fire more often than this (judged across its next 12 runs). |
app.scheduled-tasks.recheck-owner-access |
true |
all shapes | At each firing, re-check the task owner can still run the agent (agent creator or an unrevoked grant); false = skip. |
app.scheduled-tasks.retry-failed-once |
true |
all shapes | One automatic retry per failed scheduled run; false = never retry. |
app.skills.registry.api-key |
shapes that load the module | Group app.skills: Skill supply chain (core/agents com.agenticai.core.agents.skills): skills are third-party text that... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/skills/SkillRegistryClient.java) |
|
app.skills.registry.github-token |
shapes that load the module | Group app.skills: Skill supply chain (core/agents com.agenticai.core.agents.skills): skills are third-party text that... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/skills/SkillRegistryClient.java) |
|
app.skills.supply-chain.enabled |
true |
all shapes | Scan, report and record pins. |
app.skills.supply-chain.enforce |
true (false in personal; was false before 2026-10-23) |
all shapes | false = scan + report + warn only. true = a changed or unapproved high-risk custom skill is withheld until an admin re-approves it; built-in skills are never withheld. |
app.startup.background-warmup.enabled |
false |
all shapes | Cold-start lane: this runner executes on the main thread on EVERY boot, before the ApplicationReadyEvent and therefore before readiness, and does a findAll() of every agent (with its eager collections) in every... |
app.swarm.verifier.brief-chars |
1200 |
all shapes | Group app.swarm: Swarm goal verifier for delegate_parallel (docs/plans/adr/swarm-goal-verifier.md). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.swarm.verifier.max-repair-rounds |
1 |
all shapes | Group app.swarm: Swarm goal verifier for delegate_parallel (docs/plans/adr/swarm-goal-verifier.md). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.swarm.verifier.result-chars |
1500 |
all shapes | Group app.swarm: Swarm goal verifier for delegate_parallel (docs/plans/adr/swarm-goal-verifier.md). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.swarm.verifier.timeout-seconds |
30 |
all shapes | Hard wall-clock bound per verifier model call; a stall = verdict UNVERIFIED (1..120). |
app.tool-arg-policies.cache-ttl-seconds |
30 |
all shapes | How long a loaded policy set is cached per (tenant, agent) before it is re-read. |
app.tool-circuit-breaker.cooldown-minutes |
10 |
all shapes | see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolCircuitBreakerService.java |
app.tool-circuit-breaker.failure-threshold |
5 |
all shapes | 815 Governance follow-up (Fail-safe: circuit breakers on repeated failures) - see ToolCircuitBreakerService's javadoc. |
app.tool-circuit-breaker.window-minutes |
5 |
all shapes | see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolCircuitBreakerService.java |
app.tools.auto-selection.enabled |
true |
all shapes | "Automatic tool/MCP selection (no manual picking)" - ToolRelevanceSelector. |
app.tools.auto-selection.max-schema-tokens-per-turn |
3000 |
all shapes | Per-turn schema-token budget (alwaysKept + scored + seeds + siblings together). |
app.tools.auto-selection.no-match-max-tools |
4 |
all shapes | 2026-09-28 token-cost fix: a turn where NO tool scores above zero (greeting, acknowledgement, blank/attachment-only send, or a message sharing no vocabulary with any tool) gets this deliberately smaller cap instead... |
app.tools.auto-selection.semantic-enabled |
true |
shapes that load the module | see source: core/agents/src/main/java/com/agenticai/core/agents/tools/ToolRelevanceSelector.java |
app.tools.classification.trifecta-analyse-auto-select |
false |
all shapes | true: an agent with an EMPTY allow-list (Auto-select = every tool it could be offered) is analysed over the tools it would actually be offered instead of reporting no legs. |
app.tools.classification.trifecta-overrides-exempt |
true |
all shapes | true: the confirmation-gate-exempt tools above are NOT exempt for an agent that currently holds all three legs (decided per agent, cached 60s and dropped on a save/restore/capability toggle; fails safe = no exemption... |
app.tools.code.generate.max-tokens |
2048 |
shapes that load the module | Own config key, not app.agents.default-max-tokens (4096, sized for a conversational reply) - a real Java/Python program routinely needs more headroom than a paragraph of prose without running into the... |
app.tools.effect-journal.max-entries |
5000 |
all shapes | Group app.tools.effect-journal: Gap-analysis item 7 (EffectJournal): DURABLE (write/send) tools called again with the same... (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/EffectJournal.java) |
app.tools.effect-journal.store |
memory |
all shapes | Group app.tools.effect-journal: Gap-analysis item 7 (EffectJournal): DURABLE (write/send) tools called again with the same... (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/EffectJournal.java) |
app.tools.effect-journal.ttl-minutes |
30 |
all shapes | Group app.tools.effect-journal: Gap-analysis item 7 (EffectJournal): DURABLE (write/send) tools called again with the same... (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/EffectJournal.java) |
app.tools.hard-cap-per-turn |
40 |
all shapes | Defense-in-depth backstop behind auto-selection above, applied inside AgentToolProvider#capToHardLimit after the selector's own cap - see the live incident on ToolRelevanceSelector's javadoc: POST... |
app.tools.http-request.agent-allowed-hosts |
${TOOL_HTTP_REQUEST_AGENT_ALLOWED_HOSTS:} |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.allow-http |
false |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.allowed-content-types |
application/json,application/*+json,text/*,application/xm... |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.allowed-hosts |
${TOOL_HTTP_REQUEST_ALLOWED_HOSTS:} |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.credentials-require-allowlist |
true |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.denied-headers |
authorization,proxy-authorization,cookie,set-cookie,x-api... |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.enabled |
true |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.max-redirects |
3 |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.max-request-body-bytes |
65536 |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.max-response-bytes |
1048576 |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.safe-mode |
false |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.http-request.write-tool-enabled |
true |
all shapes | Group app.tools.http-request: Built-in http_request ("call any HTTP API"). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/HttpRequestSafetyConfig.java) |
app.tools.mcp-annotations.enabled |
false |
all shapes | Group app.tools.mcp-annotations: MCP tool annotations (readOnlyHint / destructiveHint / idempotentHint / openWorldHint, MCP spec 2025-06-18). (see source: core/agents/src/main/java/com/agenticai/core/agents/tools/classification/McpAnnotationClassifier.java) |
app.tools.mcp-discovery-cache-ttl-seconds |
${TOOL_MCP_DISCOVERY_CACHE_TTL_SECONDS:300} |
all shapes | Direct feedback: "mcp tools in agents screen are loading so slow" - AgentToolProvider.cachedRawToolCallbacks() was falling back to AgentToolProvider's own hardcoded default of 20s whenever this wasn't set (it never was). |
app.tools.search.threshold |
25 |
all shapes | Group app.tools.search: On-demand tool loading (ToolSearchDeferral, core/agents-tools). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents/tools/ToolResolverConfiguration.java) |
app.tutor.generate.max-per-user-per-hour |
10 |
all shapes | Course + lesson generations per member per hour (each is one or two model calls); 429 beyond it. |
app.tutor.max-curriculum-bytes |
400000 |
all shapes | Upper bound on one plan's stored curriculum JSON (lesson text + quizzes are cached in it). |
app.tutor.max-lessons-per-plan |
60 |
all shapes | Group app.tutor: Learn Anything tutor (com.agenticai.core.tutor): per-member study plans for any topic in any language,... (see source: core/agents-studio/src/main/java/com/agenticai/core/tutor/StudyPlanService.java) |
app.tutor.max-plans-per-user |
20 |
all shapes | Group app.tutor: Learn Anything tutor (com.agenticai.core.tutor): per-member study plans for any topic in any language,... (see source: core/agents-studio/src/main/java/com/agenticai/core/tutor/StudyPlanService.java) |
app.vision |
shapes that load the module | Live Vision ("Sight") - the app.vision.* config block (see application.yml): the platform-wide enable flag plus the per-user-per-day allowances VisionUsageService enforces. | |
app.vision.frames |
shapes that load the module | Group app.vision: Live Vision ("Sight") - the app.vision.* config block (see application.yml): the platform-wide... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/vision/VisionProperties.java) |
|
app.vision.watch |
shapes that load the module | Group app.vision: Live Vision ("Sight") - the app.vision.* config block (see application.yml): the platform-wide... (see source: core/agents-turn/src/main/java/com/agenticai/core/agents/vision/VisionProperties.java) |
|
app.web-search.daily-caps.brave |
0 |
all shapes | Group app.web-search.daily-caps: Attempts per 24h window the PLATFORM may make against one provider (Redis counter, fail-open). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.daily-caps.exa |
0 |
all shapes | Group app.web-search.daily-caps: Attempts per 24h window the PLATFORM may make against one provider (Redis counter, fail-open). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.daily-caps.searxng |
0 |
all shapes | Group app.web-search.daily-caps: Attempts per 24h window the PLATFORM may make against one provider (Redis counter, fail-open). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.daily-caps.serper |
0 |
all shapes | Group app.web-search.daily-caps: Attempts per 24h window the PLATFORM may make against one provider (Redis counter, fail-open). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.daily-caps.tavily |
0 |
all shapes | Group app.web-search.daily-caps: Attempts per 24h window the PLATFORM may make against one provider (Redis counter, fail-open). (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.fallback |
${WEB_SEARCH_FALLBACK:} |
all shapes | Group app.web-search: Web search providers behind web_search and deep_research. (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.provider |
brave |
all shapes | Group app.web-search: Web search providers behind web_search and deep_research. (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.searxng.base-url |
${WEB_SEARCH_SEARXNG_BASE_URL:} |
all shapes | Group app.web-search.searxng: SearXNG instance root, e.g. (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.searxng.categories |
general |
all shapes | Comma lists passed through as SearXNG's categories / engines / language parameters; blank = instance default. |
app.web-search.searxng.engines |
${WEB_SEARCH_SEARXNG_ENGINES:} |
all shapes | Group app.web-search.searxng: SearXNG instance root, e.g. (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.searxng.language |
${WEB_SEARCH_SEARXNG_LANGUAGE:} |
all shapes | Group app.web-search.searxng: SearXNG instance root, e.g. (see source: core/agents-browser/src/main/java/com/agenticai/core/agents/tools/web/WebSearchProviderWiring.java) |
app.web-search.searxng.max-results |
10 |
all shapes | Results kept per search (1..10); SearXNG returns a page of ~10-20 and has no count parameter. |
app.x402.allowed-networks |
base-sepolia |
shapes that load the module | Group app.x402: x402 pay-per-call seller surface (core/agents-tools tools/x402). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents-tools tools/x402/X402PaymentService.java) |
app.x402.enabled |
false |
shapes that load the module | Group app.x402: x402 pay-per-call seller surface (core/agents-tools tools/x402). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents-tools tools/x402/X402PaymentService.java) |
app.x402.max-payment-atomic |
1000000 |
shapes that load the module | Group app.x402: x402 pay-per-call seller surface (core/agents-tools tools/x402). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents-tools tools/x402/X402PaymentService.java) |
app.x402.public-rate-limit-per-minute |
60 |
all shapes | Anonymous request ceiling per client IP (first X-Forwarded-For hop, else socket address), sliding 60s window, in-memory per replica. |
app.x402.wallet-private-key |
shapes that load the module | Group app.x402: x402 pay-per-call seller surface (core/agents-tools tools/x402). (see source: core/agents-tools/src/main/java/com/agenticai/core/agents-tools tools/x402/X402PaymentService.java) |
|
mcp-stdio-sidecar |
shapes that load the module | mcp-stdio-sidecar.* - whether core spawns dynamic STDIO MCP servers (npx <pkg>, uvx <pkg>, ...) directly, as it always has, or routes them through the mcp-stdio-runner sidecar so the real... |
|
mcp-stdio-sidecar.address |
${MCP_STDIO_SIDECAR_URL:mcp-stdio-runner:8097} |
all shapes | host:port of the mcp-stdio-runner service, e.g. |
mcp-stdio-sidecar.shared-secret |
${MCP_STDIO_SIDECAR_SHARED_SECRET:} |
all shapes | Shared secret the relay presents to the sidecar on every spawn request. |
tool-sandbox.base-url |
${TOOL_SANDBOX_BASE_URL:http://tool-sandbox:8095} |
all shapes | Root URL of the tool-sandbox service, e.g. |
tool-sandbox.connect-timeout-millis |
5000 |
shapes that load the module | TCP connect budget (ms). |
tool-sandbox.default-memory-mb |
128 |
shapes that load the module | Per-call memory ceiling applied when a tool does not specify one. |
tool-sandbox.default-timeout-ms |
5000 |
shapes that load the module | Per-call wall clock applied when a tool does not specify one. |
tool-sandbox.enabled |
${TOOL_SANDBOX_ENABLED:false} |
all shapes | see source: core/agents/src/main/java/com/agenticai/core/agents/tools/code/ToolSandboxProperties.java |
tool-sandbox.internal-token |
${TOOL_SANDBOX_INTERNAL_TOKEN:} |
all shapes | Shared secret sent as X-Internal-Token on every call. |
tool-sandbox.max-memory-mb |
512 |
shapes that load the module | Hard ceiling a tenant's requested memory is clamped into. |
tool-sandbox.max-source-bytes |
65536 |
shapes that load the module | Largest tenant-authored program core will accept, in UTF-8 bytes. |
tool-sandbox.max-timeout-ms |
15000 |
shapes that load the module | Hard ceiling a tenant's requested timeout is clamped into. |
tool-sandbox.max-tools-per-tenant |
${TOOL_SANDBOX_MAX_TOOLS_PER_TENANT:25} |
all shapes | Standing cap on saved custom code tools per tenant schema. |
tool-sandbox.request-timeout-millis |
${TOOL_SANDBOX_REQUEST_TIMEOUT_MS:30000} |
all shapes | Whole-call budget (ms). |
Module app (22)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.rate-limit.public.checkout-per-minute |
10 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/PublicEndpointRateLimitFilter.java |
app.rate-limit.public.device-gateway-per-minute |
300 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/PublicEndpointRateLimitFilter.java |
app.rate-limit.public.scim-per-minute |
600 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/PublicEndpointRateLimitFilter.java |
app.rate-limit.public.status-per-minute |
120 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/PublicEndpointRateLimitFilter.java |
app.rate-limit.public.unsubscribe-per-minute |
30 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/PublicEndpointRateLimitFilter.java |
app.studio-app.domains.a-target |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/studioapp/StudioAppDomainService.java |
|
app.studio-app.domains.cname-target |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/studioapp/StudioAppDomainService.java |
|
app.studio-app.domains.dns-timeout-ms |
3000 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/studioapp/StudioAppDomainService.java |
app.studio-app.max-ai-per-minute-per-ip |
15 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/studioapp/StudioAppRateLimiter.java |
app.studio-app.max-auth-per-minute-per-ip |
10 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/studioapp/StudioAppRateLimiter.java |
app.studio-app.max-calls-per-minute-per-ip |
20 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/studioapp/StudioAppRateLimiter.java |
fraud-detection.api.hard-limit |
100 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/AbuseDetectionService.java |
fraud-detection.api.soft-limit |
60 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/AbuseDetectionService.java |
fraud-detection.auth.block-threshold |
10 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/AbuseDetectionService.java |
fraud-detection.auth.max-attempts |
5 |
shapes that load the module | Configuration thresholds |
fraud-detection.auto-block-duration-minutes |
60 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/AbuseDetectionService.java |
fraud-detection.enforce |
false |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/FraudDetectionInterceptor.java |
fraud-detection.hcaptcha |
shapes that load the module | Configuration properties for hCaptcha integration. | |
fraud-detection.hcaptcha.enabled |
true |
shapes that load the module | Group fraud-detection.hcaptcha: Configuration properties for hCaptcha integration. (see source: core/app/src/main/java/com/agenticai/core/common/security/HCaptchaClient.java) |
fraud-detection.hcaptcha.secret-key |
shapes that load the module | Group fraud-detection.hcaptcha: Configuration properties for hCaptcha integration. (see source: core/app/src/main/java/com/agenticai/core/common/security/HCaptchaClient.java) |
|
fraud-detection.hcaptcha.site-key |
shapes that load the module | Group fraud-detection.hcaptcha: Configuration properties for hCaptcha integration. (see source: core/app/src/main/java/com/agenticai/core/common/security/HCaptchaClient.java) |
|
fraud-detection.vpn.score-threshold |
0.7 |
shapes that load the module | see source: core/app/src/main/java/com/agenticai/core/common/security/AbuseDetectionService.java |
Module app-config (467)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
agent-supervisor.sweep.enabled |
true |
all shapes | see source: core/app/src/main/java/com/agenticai/core/common/improve/AgentSupervisorSweep.java |
app.a2a.signing.key-id |
${APP_A2A_SIGNING_KEY_ID:} |
all shapes | Group app.a2a.signing: Card signing - see A2aCardSigningService's javadoc. (see source: core/app-config/src/main/resources/application.yml:786) |
app.a2a.signing.private-key-pem |
${APP_A2A_SIGNING_PRIVATE_KEY:} |
all shapes | Group app.a2a.signing: Card signing - see A2aCardSigningService's javadoc. (see source: core/app-config/src/main/resources/application.yml:784) |
app.a2a.signing.public-key-pem |
${APP_A2A_SIGNING_PUBLIC_KEY:} |
all shapes | Group app.a2a.signing: Card signing - see A2aCardSigningService's javadoc. (see source: core/app-config/src/main/resources/application.yml:785) |
app.agent-jobs.approval.max-parks-per-job |
3 |
all shapes | Each park buys exactly ONE more turn, and each resumed turn charges the requester's budget again - so this is a real spend bound. |
app.agent-jobs.approval.park-timeout |
${AGENT_JOB_APPROVAL_TIMEOUT:PT24H} |
all shapes | A day: parking exists so a job can legitimately wait for someone's working hours. |
app.agent-jobs.approval.sweep-interval |
PT60S |
all shapes | The sweep is the GUARANTEE that an approved job resumes. |
app.agent-jobs.durability.max-attempts |
${AGENT_JOB_MAX_ATTEMPTS:2} |
all shapes | Total claims of one job INCLUDING the first, compared against agent_job.attempt_count. |
app.agent-jobs.durability.resume-rerun-incomplete-turn |
${AGENT_JOB_RESUME_RERUN_INCOMPLETE_TURN:false} |
all shapes | Group app.agent-jobs.durability: Read by the DURABILITY half (orphan recovery). (see source: core/app-config/src/main/resources/application.yml:1011) |
app.agent-jobs.durability.step-journal-purge-interval |
${AGENT_JOB_STEP_JOURNAL_PURGE_INTERVAL:6h} |
all shapes | Group app.agent-jobs.durability: Read by the DURABILITY half (orphan recovery). (see source: core/app-config/src/main/resources/application.yml:1013) |
app.agent-jobs.durability.step-journal-retention-days |
${AGENT_JOB_STEP_JOURNAL_RETENTION_DAYS:14} |
all shapes | Group app.agent-jobs.durability: Read by the DURABILITY half (orphan recovery). (see source: core/app-config/src/main/resources/application.yml:1012) |
app.agent-jobs.output-schema.max-repair-attempts |
2 |
all shapes | Group app.agent-jobs.output-schema: Read by the OBSERVABILITY half (structured job output). (see source: core/app-config/src/main/resources/application.yml:884) |
app.agent-jobs.output-schema.max-schema-bytes |
32768 |
all shapes | Group app.agent-jobs.output-schema: Read by the OBSERVABILITY half (structured job output). (see source: core/app-config/src/main/resources/application.yml:885) |
app.agent-jobs.ownership.enabled |
${AGENT_JOB_OWNERSHIP_ENABLED:false} |
all shapes | Group app.agent-jobs.ownership: Cross-replica job OWNERSHIP LEASE + adoption (core/agents job/ownership). (see source: core/app-config/src/main/resources/application.yml:1025) |
app.agent-jobs.ownership.heartbeat-seconds |
${AGENT_JOB_HEARTBEAT_SECONDS:15} |
all shapes | Group app.agent-jobs.ownership: Cross-replica job OWNERSHIP LEASE + adoption (core/agents job/ownership). (see source: core/app-config/src/main/resources/application.yml:1027) |
app.agent-jobs.ownership.lease-seconds |
${AGENT_JOB_LEASE_SECONDS:60} |
all shapes | Group app.agent-jobs.ownership: Cross-replica job OWNERSHIP LEASE + adoption (core/agents job/ownership). (see source: core/app-config/src/main/resources/application.yml:1026) |
app.agent-jobs.ownership.resume-max-attempts |
${AGENT_JOB_RESUME_MAX_ATTEMPTS:3} |
all shapes | Group app.agent-jobs.ownership: Cross-replica job OWNERSHIP LEASE + adoption (core/agents job/ownership). (see source: core/app-config/src/main/resources/application.yml:1028) |
app.agent-jobs.ownership.sweep-batch |
${AGENT_JOB_LEASE_SWEEP_BATCH:50} |
all shapes | Group app.agent-jobs.ownership: Cross-replica job OWNERSHIP LEASE + adoption (core/agents job/ownership). (see source: core/app-config/src/main/resources/application.yml:1030) |
app.agent-jobs.ownership.sweep-interval |
${AGENT_JOB_LEASE_SWEEP_INTERVAL:60s} |
all shapes | Group app.agent-jobs.ownership: Cross-replica job OWNERSHIP LEASE + adoption (core/agents job/ownership). (see source: core/app-config/src/main/resources/application.yml:1029) |
app.agent-jobs.progress.dedupe-window-seconds |
30 |
all shapes | Group app.agent-jobs.progress: Read by the OBSERVABILITY half (progress notes + the report_progress tool). (see source: core/app-config/src/main/resources/application.yml:890) |
app.agent-jobs.progress.max-note-chars |
500 |
all shapes | Group app.agent-jobs.progress: Read by the OBSERVABILITY half (progress notes + the report_progress tool). (see source: core/app-config/src/main/resources/application.yml:889) |
app.agent-jobs.progress.max-notes-per-job |
200 |
all shapes | Group app.agent-jobs.progress: Read by the OBSERVABILITY half (progress notes + the report_progress tool). (see source: core/app-config/src/main/resources/application.yml:888) |
app.agent-jobs.schedule.blocked-alert-after |
PT4H |
all shapes | Group app.agent-jobs.schedule: Read by the SCHEDULES half. (see source: core/app-config/src/main/resources/application.yml:898) |
app.agent-jobs.schedule.max-per-tenant |
25 |
all shapes | Group app.agent-jobs.schedule: Read by the SCHEDULES half. (see source: core/app-config/src/main/resources/application.yml:899) |
app.agent-jobs.schedule.min-interval-minutes |
5 |
all shapes | Group app.agent-jobs.schedule: Read by the SCHEDULES half. (see source: core/app-config/src/main/resources/application.yml:900) |
app.agent-jobs.schedule.skip-notification-threshold |
3 |
all shapes | Group app.agent-jobs.schedule: Read by the SCHEDULES half. (see source: core/app-config/src/main/resources/application.yml:897) |
app.agent-jobs.trigger.duplicate-window |
${AGENT_JOB_TRIGGER_DUPLICATE_WINDOW:PT5M} |
all shapes | DUPLICATE-DELIVERY guard for the eight CHANNEL event types, whose runId is a fresh random UUID per publish (WorkflowEventPublisher#publishNewRun) and therefore useless as an idempotency key across a provider retry -... |
app.agent-jobs.trigger.enabled |
${AGENT_JOB_TRIGGERS_ENABLED:true} |
all shapes | Group app.agent-jobs.trigger: Read by the EVENT-TRIGGERS half. (see source: core/app-config/src/main/resources/application.yml:906) |
app.agent-jobs.trigger.max-fires-per-sustained-window |
${AGENT_JOB_TRIGGER_MAX_FIRES_PER_DAY:200} |
all shapes | SUSTAINED half, and the one that catches the cycle this guard was actually written for. |
app.agent-jobs.trigger.max-fires-per-window |
${AGENT_JOB_TRIGGER_MAX_FIRES:20} |
all shapes | THE LOOP GUARD, in two halves. |
app.agent-jobs.trigger.max-goal-chars |
4000 |
all shapes | Ceiling on a rendered goal template, so one event cannot mint a huge prompt. |
app.agent-jobs.trigger.max-tenant-fires-per-sustained-window |
${AGENT_JOB_TRIGGER_MAX_TENANT_FIRES_PER_DAY:1000} |
all shapes | Group app.agent-jobs.trigger: Read by the EVENT-TRIGGERS half. (see source: core/app-config/src/main/resources/application.yml:953) |
app.agent-jobs.trigger.max-tenant-fires-per-window |
${AGENT_JOB_TRIGGER_MAX_TENANT_FIRES:100} |
all shapes | TENANT-WIDE ceilings over the SAME two windows, counted across ALL of a tenant's triggers (the firing ledger lives in the tenant's schema, so counting the whole table is the per-tenant count). |
app.agent-jobs.trigger.max-triggers-per-agent |
10 |
all shapes | Group app.agent-jobs.trigger: Read by the EVENT-TRIGGERS half. (see source: core/app-config/src/main/resources/application.yml:907) |
app.agent-jobs.trigger.max-triggers-per-event |
10 |
all shapes | Bounds the work one event does on the PUBLISHING thread (a webhook request thread) - a hard cap, not advice. |
app.agent-jobs.trigger.sustained-window |
PT24H |
all shapes | Group app.agent-jobs.trigger: Read by the EVENT-TRIGGERS half. (see source: core/app-config/src/main/resources/application.yml:941) |
app.agent-jobs.trigger.window |
PT5M |
all shapes | Group app.agent-jobs.trigger: Read by the EVENT-TRIGGERS half. (see source: core/app-config/src/main/resources/application.yml:925) |
app.approvals.retry.base-delay-seconds |
30 |
all shapes | see source: core/approval/src/main/java/com/agenticai/core/approval/HumanApprovalGateService.java |
app.approvals.retry.max-attempts |
5 |
all shapes | Autonomy/Guardrails Strategy Stage C. |
app.approvals.retry.max-delay-seconds |
3600 |
all shapes | see source: core/approval/src/main/java/com/agenticai/core/approval/HumanApprovalGateService.java |
app.atlassian.oauth.redirect-uri |
${ATLASSIAN_OAUTH_REDIRECT_URI:} |
all shapes | Confluence/Jira MCP connector (AtlassianOAuthService) - unlike X's BYOK, Atlassian's remote MCP server supports Dynamic Client Registration, so there is no client-id/client-secret here either: the platform... |
app.audit.seal.enabled |
false |
all shapes | Group app.audit.seal: Tamper-evident audit log (core/audit seal/AuditSealService, sweep in core/platform... (see source: core/platform/src/main/java/com/agenticai/core/platform/auditseal/AuditSealSweep.java) |
app.audit.seal.interval |
${AUDIT_SEAL_INTERVAL:15m} |
all shapes | Group app.audit.seal: Tamper-evident audit log (core/audit seal/AuditSealService, sweep in core/platform... (see source: core/app-config/src/main/resources/application.yml:3089) |
app.audit.seal.max-defer-seconds |
3600 |
all shapes | Sealing also stops short of the oldest open DB transaction (it may still commit older-stamped rows); this caps how far back that deferral may reach, so a stuck session cannot block sealing forever. |
app.audit.seal.max-rows-per-seal |
5000 |
all shapes | Group app.audit.seal: Tamper-evident audit log (core/audit seal/AuditSealService, sweep in core/platform... (see source: core/audit/src/main/java/com/agenticai/core/audit/seal/AuditSealService.java) |
app.audit.seal.max-tick-seconds |
600 |
all shapes | Time budget of one sweep tick (the advisory-lock connection is held meanwhile); the rest waits a tick. |
app.audit.seal.settle-seconds |
120 |
all shapes | Only rows older than this are sealed, so a row whose transaction commits late is not skipped by a seal. |
app.billing.dunning.max-failed-attempts |
3 |
all shapes | Group app.billing.dunning: D5 dunning (SubscriptionPendingHandler): how many failed subscription payment attempts (Razorpay... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/SubscriptionPendingHandler.java) |
app.billing.phonepe.api-base-url |
${PHONEPE_API_BASE_URL:} |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.phonepe.client-secret |
${PHONEPE_CLIENT_SECRET:} |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.phonepe.client-version |
${PHONEPE_CLIENT_VERSION:1} |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.phonepe.merchant-id |
${PHONEPE_MERCHANT_ID:} |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.phonepe.oauth-base-url |
https://api-preprod.phonepe.com/apis/pg-sandbox |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.phonepe.plan-premium |
${PHONEPE_PLAN_PREMIUM:} |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.phonepe.plan-unlimited |
${PHONEPE_PLAN_UNLIMITED:} |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.phonepe.webhook-password |
${PHONEPE_WEBHOOK_PASSWORD:} |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.phonepe.webhook-username |
${PHONEPE_WEBHOOK_USERNAME:} |
all shapes | Group app.billing.phonepe: UPI multi-gateway support (2026-07-19) - PhonePeGatewayClient (@Profile("security")), the second... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/PhonePeGatewayClient.java) |
app.billing.razorpay.key-secret |
${RAZORPAY_KEY_SECRET:} |
all shapes | Group app.billing.razorpay: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency), B2 (annual plans), D4 (coupons via Razorpay... (see source: core/billing/src/main/java/com/agenticai/core/billing/commerce/RazorpayPaidPaymentLookup.java) |
app.billing.razorpay.offers |
{} |
all shapes | D4 coupons: couponCode -> Razorpay Offer id (dashboard-created). |
app.billing.razorpay.plan-id-business |
${RAZORPAY_PLAN_ID_BUSINESS:} |
all shapes | Group app.billing.razorpay: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency), B2 (annual plans), D4 (coupons via Razorpay... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/RazorpayPlanCatalog.java) |
app.billing.razorpay.plan-id-starter |
${RAZORPAY_PLAN_ID_STARTER:} |
all shapes | The legacy pre-B1 "one Plan id per paid tier" keys (created when the paid tiers were still named STARTER/BUSINESS - see TenantUpgradeService's javadoc). |
app.billing.razorpay.plans.premium.inr.annual |
${RAZORPAY_PLAN_PREMIUM_INR_ANNUAL:} |
all shapes | Group app.billing.razorpay.plans: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created... (see source: core/app-config/src/main/resources/application.yml:2077) |
app.billing.razorpay.plans.premium.inr.monthly |
${RAZORPAY_PLAN_PREMIUM_INR_MONTHLY:} |
all shapes | Group app.billing.razorpay.plans: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created... (see source: core/app-config/src/main/resources/application.yml:2076) |
app.billing.razorpay.plans.premium.usd.annual |
${RAZORPAY_PLAN_PREMIUM_USD_ANNUAL:} |
all shapes | Group app.billing.razorpay.plans: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created... (see source: core/app-config/src/main/resources/application.yml:2080) |
app.billing.razorpay.plans.premium.usd.monthly |
${RAZORPAY_PLAN_PREMIUM_USD_MONTHLY:} |
all shapes | Group app.billing.razorpay.plans: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created... (see source: core/app-config/src/main/resources/application.yml:2079) |
app.billing.razorpay.plans.unlimited.inr.annual |
${RAZORPAY_PLAN_UNLIMITED_INR_ANNUAL:} |
all shapes | Group app.billing.razorpay.plans: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created... (see source: core/app-config/src/main/resources/application.yml:2084) |
app.billing.razorpay.plans.unlimited.inr.monthly |
${RAZORPAY_PLAN_UNLIMITED_INR_MONTHLY:} |
all shapes | Group app.billing.razorpay.plans: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created... (see source: core/app-config/src/main/resources/application.yml:2083) |
app.billing.razorpay.plans.unlimited.usd.annual |
${RAZORPAY_PLAN_UNLIMITED_USD_ANNUAL:} |
all shapes | Group app.billing.razorpay.plans: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created... (see source: core/app-config/src/main/resources/application.yml:2087) |
app.billing.razorpay.plans.unlimited.usd.monthly |
${RAZORPAY_PLAN_UNLIMITED_USD_MONTHLY:} |
all shapes | Group app.billing.razorpay.plans: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created... (see source: core/app-config/src/main/resources/application.yml:2086) |
app.billing.razorpay.trial-days |
${RAZORPAY_TRIAL_DAYS:14} |
all shapes | Days of free trial before the first real charge, applied uniformly across both paid tiers per the "Free trial, card required upfront" billing decision. |
app.billing.razorpay.webhook-secret |
${RAZORPAY_WEBHOOK_SECRET:} |
all shapes | Group app.billing.razorpay: PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency), B2 (annual plans), D4 (coupons via Razorpay... (see source: core/billing/src/main/java/com/agenticai/core/billing/gateway/RazorpayGatewayClient.java) |
app.billing.stripe.price-id-unlimited |
${STRIPE_PRICE_ID_UNLIMITED:} |
all shapes | Injected builder for the same reason PhonePeGatewayClient's constructor documents: a hand-built RestClient bypasses Spring's timeout configuration, which parks a checkout request forever on a gateway outage. |
app.chat.branching.retention-purge-interval |
${APP_CHAT_BRANCHING_RETENTION_PURGE_INTERVAL:6h} |
all shapes | Group app.chat.branching: Chat branching (ChatBranchService, core/agents): edit an earlier user message, regenerate a reply, and... (see source: core/app-config/src/main/resources/application.yml:1228) |
app.chat.tools.result-clearing.enabled |
true |
all shapes | Group app.chat.tools.result-clearing: In-turn tool-result clearing (gap analysis item 4): once the tool results accumulated in ONE turn exceed... (see source: core/chat/src/main/java/com/agenticai/core/chat/ResilientToolCallingManagerFactory.java) |
app.chat.tools.result-clearing.keep-last |
3 |
all shapes | Group app.chat.tools.result-clearing: the newest tool results of one reply that are never cleared (see Tool calls: budget, rounds and result size) |
app.chat.tools.result-clearing.restore-hint |
call the tool again if you need this result |
all shapes | Group app.chat.tools.result-clearing: the model-facing restore field of the JSON placeholder that replaces a cleared result |
app.chat.tools.result-clearing.threshold-chars |
24000 |
all shapes | Group app.chat.tools.result-clearing: total tool-result characters of one reply above which older results are cleared |
app.coding-harness.allow-local-runner |
false |
all shapes | see source: core/sdlc/src/main/java/com/agenticai/core/sdlc/harness/CodingSandboxFactory.java |
app.coding-harness.allow-platform-e2b |
false |
all shapes | The workspace's own E2B key is always used; the platform fallback key only when this is true (it is metered per short run_code call, a multi-minute task would bypass that). |
app.coding-harness.allowed-runners |
mvn,gradle,npm,pytest,go |
all shapes | Test runners a task may use (subset of mvn,gradle,npm,pytest,go - it can only be narrowed). |
app.coding-harness.command-timeout-seconds |
100 |
all shapes | Per-command wall time inside the sandbox (5..115: the HTTP read timeout to the sandbox is 120s). |
app.coding-harness.e2b-template-id |
${CODING_HARNESS_E2B_TEMPLATE_ID:} |
all shapes | see source: core/sdlc/src/main/java/com/agenticai/core/sdlc/harness/CodingSandboxFactory.java |
app.coding-harness.local-bash |
bash |
all shapes | see source: core/sdlc/src/main/java/com/agenticai/core/sdlc/harness/CodingSandboxFactory.java |
app.coding-harness.local-workspace-dir |
${CODING_HARNESS_LOCAL_WORKSPACE_DIR:} |
all shapes | Owner-only (rwx------) parent directory the local runner creates its workspaces under - never the shared system temp dir. |
app.datasource.aws.region |
${DB_AWS_REGION:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:489 |
app.datasource.azure.scope |
${DB_AZURE_SCOPE:https://ossrdbms-aad.database.windows.ne... |
all shapes | see source: core/app-config/src/main/resources/application.yml:491 |
app.datasource.gcp.scope |
${DB_GCP_SCOPE:https://www.googleapis.com/auth/sqlservice... |
all shapes | see source: core/app-config/src/main/resources/application.yml:493 |
app.datasource.token-refresh-skew |
${DB_TOKEN_REFRESH_SKEW:2m} |
all shapes | Fetch a new token this long before the cached one expires. |
app.demo.whatsapp.access-token |
${DEMO_WHATSAPP_ACCESS_TOKEN:} |
all shapes | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoWhatsAppBot.java |
app.demo.whatsapp.app-secret |
${DEMO_WHATSAPP_APP_SECRET:} |
all shapes | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoWhatsAppBot.java |
app.demo.whatsapp.business-number-e164 |
${DEMO_WHATSAPP_BUSINESS_NUMBER_E164:} |
all shapes | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoWhatsAppBot.java |
app.demo.whatsapp.id-hash-key |
${DEMO_WHATSAPP_ID_HASH_KEY:} |
all shapes | Optional separate key for hashing visitor numbers (defaults to app-secret). |
app.demo.whatsapp.max-pairs-per-phone |
3 |
all shapes | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoWhatsAppWebhookController.java |
app.demo.whatsapp.max-turns-per-phone |
60 |
all shapes | Lifetime per-phone bounds across ALL sandboxes that phone ever pairs. |
app.demo.whatsapp.phone-number-id |
${DEMO_WHATSAPP_PHONE_NUMBER_ID:} |
all shapes | try Agent Studio "Deploy" WhatsApp card (DemoWhatsAppBot). |
app.demo.whatsapp.verify-token |
${DEMO_WHATSAPP_VERIFY_TOKEN:} |
all shapes | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoWhatsAppBot.java |
app.embeddings.api-key |
${APP_EMBEDDINGS_API_KEY:} |
profile knowledge, shapes that load the module |
Bearer token; blank sends no Authorization header (e.g. |
app.embeddings.batch-size |
${APP_EMBEDDINGS_BATCH_SIZE:64} |
profile knowledge, shapes that load the module |
Max inputs per request. |
app.embeddings.send-dimensions |
${APP_EMBEDDINGS_SEND_DIMENSIONS:false} |
profile knowledge, shapes that load the module |
Also send "dimensions": N in the request (OpenAI text-embedding-3-* support server-side truncation). |
app.embeddings.timeout |
${APP_EMBEDDINGS_TIMEOUT:30s} |
profile knowledge, shapes that load the module |
Per-request read timeout. |
app.encryption.key-previous |
${APP_ENCRYPTION_KEY_PREVIOUS:} |
all shapes | 815 Governance follow-up - optional. |
app.google.oauth.client-id |
${GOOGLE_OAUTH_CLIENT_ID:} |
all shapes | Google Workspace integration (GoogleOAuthService) - one platform- level OAuth 2.0 Client (type "Web application") in Google Cloud Console, shared by every tenant - see that class's javadoc for why this can't be BYOK... |
app.google.oauth.client-secret |
${GOOGLE_OAUTH_CLIENT_SECRET:} |
all shapes | see source: core/google/src/main/java/com/agenticai/core/google/GoogleOAuthService.java |
app.google.oauth.mode |
${GOOGLE_OAUTH_MODE:web} |
all shapes | web = Web-application client with the fixed redirect-uri below (domain, Compose full, Kubernetes). loopback = Desktop-app client + PKCE, redirect must be localhost/127.0.0.1; the personal profile defaults to it. |
app.google.oauth.redirect-uri |
${GOOGLE_OAUTH_REDIRECT_URI:} |
all shapes | Must exactly match one of the "Authorized redirect URIs" configured on the Google Cloud OAuth Client, and must point at this backend (not the web console) - e.g. |
app.graph-memory.arbitration.enabled |
false |
all shapes | Master Roadmap 2026 item #11 (Memory write arbitration, Mem0-style) - see MemoryWriteArbitrationService's javadoc. |
app.guardrails.procedures.session-calls-store |
memory |
all shapes | memory (default) or redis; field-injected so a hand-built instance keeps the default. |
app.http.hcaptcha.read-timeout |
${HCAPTCHA_READ_TIMEOUT:5s} |
all shapes | Group app.http: Per-dependency outbound timeouts for clients that build their own transport (everything else is bounded by... (see source: core/app-config/src/main/resources/application.yml:2854) |
app.jev.api-key |
(set in source) |
all shapes | Group app.jev: Jev - TypeSafe AI's System One decision model (JevDecisionService, core/agents/decision). (see source: core/app-config/src/main/resources/application.yml:718) |
app.knowledge.fusion.enabled |
false |
all shapes | Group app.knowledge.fusion: fusion: tunable Reciprocal Rank Fusion over the vector + keyword lists (k 60 / weights 1.0 is what runs... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.fusion.k |
60 |
all shapes | Group app.knowledge.fusion: fusion: tunable Reciprocal Rank Fusion over the vector + keyword lists (k 60 / weights 1.0 is what runs... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.fusion.keyword-weight |
1.0 |
all shapes | Group app.knowledge.fusion: fusion: tunable Reciprocal Rank Fusion over the vector + keyword lists (k 60 / weights 1.0 is what runs... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.fusion.mmr-lambda |
${KNOWLEDGE_FUSION_MMR_LAMBDA:} |
all shapes | Group app.knowledge.fusion: fusion: tunable Reciprocal Rank Fusion over the vector + keyword lists (k 60 / weights 1.0 is what runs... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.fusion.vector-weight |
1.0 |
all shapes | Group app.knowledge.fusion: fusion: tunable Reciprocal Rank Fusion over the vector + keyword lists (k 60 / weights 1.0 is what runs... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.ingestion.claiming.enabled |
false |
all shapes | Group app.knowledge.ingestion.claiming: Opt-in per-job claiming (ADR-0014): no poll-wide advisory lock; each job is claimed with FOR UPDATE SKIP... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeIngestionJobRunner.java) |
app.knowledge.ingestion.claiming.lease-seconds |
300 |
all shapes | Group app.knowledge.ingestion.claiming: Opt-in per-job claiming (ADR-0014): no poll-wide advisory lock; each job is claimed with FOR UPDATE SKIP... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeIngestionJobRunner.java) |
app.knowledge.ingestion.claiming.max-attempts |
3 |
all shapes | A job whose runner stopped heartbeating is re-run at most this many times in total, then FAILED. |
app.knowledge.ingestion.max-jobs-per-tenant-per-tick |
2 |
all shapes | Group app.knowledge.ingestion: Background job fairness (scale/reliability, 2026-10-12). (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeIngestionJobRunner.java) |
app.knowledge.ingestion.tick-budget-seconds |
120 |
all shapes | Group app.knowledge.ingestion: Background job fairness (scale/reliability, 2026-10-12). (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeIngestionJobRunner.java) |
app.knowledge.parent-child.child-tokens |
200 |
all shapes | Group app.knowledge.parent-child: RAG quality (retrieval lane, 2026-10-04). (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.parent-child.enabled |
false |
all shapes | Group app.knowledge.parent-child: RAG quality (retrieval lane, 2026-10-04). (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.parent-child.parent-tokens |
1000 |
all shapes | Group app.knowledge.parent-child: RAG quality (retrieval lane, 2026-10-04). (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.query-rewrite.enabled |
false |
all shapes | Group app.knowledge.query-rewrite: query-rewrite: ONE bounded cheap-model call per search (multi = alternative queries, hyde = one... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.query-rewrite.expansion-weight |
0.5 |
all shapes | Group app.knowledge.query-rewrite: query-rewrite: ONE bounded cheap-model call per search (multi = alternative queries, hyde = one... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeSearchService.java) |
app.knowledge.query-rewrite.max-variants |
3 |
all shapes | Group app.knowledge.query-rewrite: query-rewrite: ONE bounded cheap-model call per search (multi = alternative queries, hyde = one... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.query-rewrite.mode |
multi |
all shapes | Group app.knowledge.query-rewrite: query-rewrite: ONE bounded cheap-model call per search (multi = alternative queries, hyde = one... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagRetrievalConfig.java) |
app.knowledge.query-rewrite.timeout-ms |
4000 |
all shapes | Group app.knowledge.query-rewrite: query-rewrite: ONE bounded cheap-model call per search (multi = alternative queries, hyde = one... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/QueryRewriter.java) |
app.knowledge.rag-eval.free-max-questions |
25 |
all shapes | Group app.knowledge.rag-eval: rag-eval: POST /api/knowledge/{collectionId}/eval caps (the plan gate reuses app.evals.free-plan-enabled;... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagEvalService.java) |
app.knowledge.rag-eval.max-questions |
200 |
all shapes | Group app.knowledge.rag-eval: rag-eval: POST /api/knowledge/{collectionId}/eval caps (the plan gate reuses app.evals.free-plan-enabled;... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/rag/RagEvalService.java) |
app.knowledge.rag-eval.max-seconds |
80 |
all shapes | 80s: the Cloudflare edge answers 524 at about 100s, so a synchronous run must stop well before that (a question already in flight can still finish after the deadline). |
app.knowledge.reembed.max-jobs-per-tenant-per-tick |
1 |
all shapes | see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeReembedJobRunner.java |
app.knowledge.reembed.tick-budget-seconds |
300 |
all shapes | see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeReembedJobRunner.java |
app.knowledge.span-citations.enabled |
false |
all shapes | Group app.knowledge.span-citations: Span-level citations: search_knowledge_base hits gain startChar/endChar (offsets into the hit's chunk... (see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeSearchService.java) |
app.llm.anthropic.thinking.effort |
${LLM_ANTHROPIC_THINKING_EFFORT:} |
all shapes | see source: core/chat/src/main/java/com/agenticai/core/chat/AnthropicAgentChatClientFactory.java |
app.llm.anthropic.thinking.enabled |
true |
all shapes | see source: core/chat/src/main/java/com/agenticai/core/chat/AnthropicAgentChatClientFactory.java |
app.llm.gateway.budget.free-monthly-cap-usd |
0 |
all shapes | see source: core/chat/src/main/java/com/agenticai/core/chat/gateway/LlmGatewayBudgetService.java |
app.llm.gateway.rate-limit.free-max-calls-per-minute |
30 |
all shapes | Infra audit (2026-07-27) - was a single flat 120/min for every tenant regardless of plan; now plan-scoped (see LlmGatewayRateLimiterService's javadoc), same "FREE baseline, PREMIUM mid, UNLIMITED... |
app.llm.gateway.rate-limit.premium-max-calls-per-minute |
150 |
all shapes | Group app.llm.gateway.rate-limit: 848 (LLM gateway seam) - see LlmGatewayService's javadoc. (see source: core/chat/src/main/java/com/agenticai/core/chat/gateway/LlmGatewayRateLimiterService.java) |
app.llm.gateway.rate-limit.unlimited-max-calls-per-minute |
600 |
all shapes | Group app.llm.gateway.rate-limit: 848 (LLM gateway seam) - see LlmGatewayService's javadoc. (see source: core/chat/src/main/java/com/agenticai/core/chat/gateway/LlmGatewayRateLimiterService.java) |
app.llm.structured-output.anthropic-tool-forcing.enabled |
false |
all shapes | Anthropic native structured output via forced tool use (see AnthropicStructuredToolChatModel). |
app.llm.structured-output.native-enabled |
false |
all shapes | Opt-in for provider-enforced JSON schemas (OpenAI json_schema / Gemini responseSchema). |
app.llm.structured-output.ollama-format.enabled |
false |
all shapes | Ollama response_format: json_schema (its format parameter). |
app.llm.tool-call-probe.enabled |
true |
all shapes | When a hosted-pool row is saved, send the model one two-tool chat request and store how it made the call (tool_call_grade: STRUCTURED, TEXT or NONE). Ollama rows whose /api/show capabilities lack tools are graded NONE without a call. Off: rows keep whatever grade they have (null = not measured). See Run free with local models. |
app.llm.tools.strict.enabled |
false |
all shapes | OpenAI strict: true on function tools whose schema satisfies the strict subset. |
app.mail.from |
${WORKFLOW_MAIL_FROM:workflows@agentic-ai.local} |
profile lite, profile workflow |
see source: core/app-config/src/main/resources/application-lite.yml:46 |
app.mcp-server.expose-agents.agent-timeout-seconds |
90 |
profile mcpserver, shapes that load the module |
Longest an agent_* call may hold the request before it gives up. |
app.mcp-server.expose-agents.max-body-bytes |
262144 |
profile mcpserver, shapes that load the module |
Largest JSON-RPC request body inspected (and accepted) while the feature is on. |
app.mcp-server.expose-agents.max-input-chars |
16000 |
profile mcpserver, shapes that load the module |
Group app.mcp-server: "Agents and workflows as MCP tools" (core/mcpserver expose package): with enabled=true a caller's... (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/expose/McpExposureConfig.java) |
app.mcp-server.expose-agents.max-invocations-per-minute |
30 |
profile mcpserver, shapes that load the module |
Per-API-key cap on exposed-tool invocations (a real agent turn / workflow run), on top of app.mcpserver.rate-limit which counts every request. |
app.mcp-server.expose-agents.max-output-chars |
20000 |
profile mcpserver, shapes that load the module |
Group app.mcp-server: "Agents and workflows as MCP tools" (core/mcpserver expose package): with enabled=true a caller's... (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/expose/McpExposureConfig.java) |
app.mcp-server.expose-agents.poll-interval-ms |
1000 |
profile mcpserver, shapes that load the module |
Group app.mcp-server: "Agents and workflows as MCP tools" (core/mcpserver expose package): with enabled=true a caller's... (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/expose/McpExposureConfig.java) |
app.mcp-server.expose-agents.workflow-wait-seconds |
45 |
profile mcpserver, shapes that load the module |
A workflow_* call waits this long, then returns status RUNNING + run_id (poll with get_run). |
app.mcp.oauth.cimd.client-id-url |
${MCP_OAUTH_CIMD_CLIENT_ID_URL:} |
all shapes | Group app.mcp.oauth.cimd: OAuth Client ID Metadata Document (CIMD): the platform publishes ONE client metadata document at a stable... (see source: core/mcp/src/main/java/com/agenticai/core/mcp/oauth/McpOAuthClientMetadata.java) |
app.mcp.oauth.cimd.client-name |
VegaDuta |
all shapes | Group app.mcp.oauth.cimd: OAuth Client ID Metadata Document (CIMD): the platform publishes ONE client metadata document at a stable... (see source: core/mcp/src/main/java/com/agenticai/core/mcp/oauth/McpOAuthClientMetadata.java) |
app.mcp.oauth.cimd.client-uri |
${MCP_OAUTH_CIMD_CLIENT_URI:} |
all shapes | Group app.mcp.oauth.cimd: OAuth Client ID Metadata Document (CIMD): the platform publishes ONE client metadata document at a stable... (see source: core/mcp/src/main/java/com/agenticai/core/mcp/oauth/McpOAuthClientMetadata.java) |
app.mcp.oauth.cimd.enabled |
false |
all shapes | Group app.mcp.oauth.cimd: OAuth Client ID Metadata Document (CIMD): the platform publishes ONE client metadata document at a stable... (see source: core/mcp/src/main/java/com/agenticai/core/mcp/oauth/McpOAuthClientMetadata.java) |
app.mcp.oauth.cimd.path |
${MCP_OAUTH_CIMD_PATH:/oauth/mcp/remote/client-metadata.json} |
all shapes | Group app.mcp.oauth.cimd: OAuth Client ID Metadata Document (CIMD): the platform publishes ONE client metadata document at a stable... (see source: core/mcp/src/main/java/com/agenticai/core/mcp/oauth/McpOAuthClientMetadata.java) |
app.mcp.oauth.max-response-bytes |
262144 |
all shapes | see source: core/mcp/src/main/java/com/agenticai/core/mcp/oauth/JdkMcpOAuthHttp.java |
app.mcp.oauth.provider-allowed-return-origins |
${MCP_OAUTH_PROVIDER_ALLOWED_RETURN_ORIGINS:} |
all shapes | Same console as the provider flow: no localhost default (RULE 0) - blank keeps the feature unusable. |
app.mcp.oauth.provider-callback-base-url |
${MCP_OAUTH_PROVIDER_CALLBACK_BASE_URL:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1950 |
app.mcp.oauth.provider-console-redirect-uri |
${MCP_OAUTH_PROVIDER_CONSOLE_REDIRECT_URI:} |
all shapes | Same console as the provider flow: no localhost default (RULE 0) - blank keeps the feature unusable. |
app.mcp.oauth.provider-state-ttl-seconds |
${MCP_OAUTH_PROVIDER_STATE_TTL_SECONDS:600} |
all shapes | Same console as the provider flow: no localhost default (RULE 0) - blank keeps the feature unusable. |
app.mcp.oauth.providers-enabled |
${MCP_OAUTH_PROVIDERS_ENABLED:false} |
all shapes | One-click "Connect" for popular SaaS tools (OAuthProviderRegistry / OAuthProviderService): authorization-code + PKCE, tokens encrypted per tenant (table mcp_oauth_connection, V424), automatic refresh, revoke on... |
app.mcp.oauth.providers.asana.client-id |
${MCP_OAUTH_ASANA_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2012 |
app.mcp.oauth.providers.asana.client-secret |
${MCP_OAUTH_ASANA_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2013 |
app.mcp.oauth.providers.asana.enabled |
${MCP_OAUTH_ASANA_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2014 |
app.mcp.oauth.providers.github.client-id |
${MCP_OAUTH_GITHUB_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1988 |
app.mcp.oauth.providers.github.client-secret |
${MCP_OAUTH_GITHUB_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1989 |
app.mcp.oauth.providers.github.enabled |
${MCP_OAUTH_GITHUB_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1990 |
app.mcp.oauth.providers.google-calendar.client-id |
${MCP_OAUTH_GOOGLE_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1984 |
app.mcp.oauth.providers.google-calendar.client-secret |
${MCP_OAUTH_GOOGLE_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1985 |
app.mcp.oauth.providers.google-calendar.enabled |
${MCP_OAUTH_GOOGLE_CALENDAR_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1986 |
app.mcp.oauth.providers.google-drive.client-id |
${MCP_OAUTH_GOOGLE_CLIENT_ID:} |
all shapes | Group app.mcp.oauth.providers.google-drive: drive.readonly and gmail.readonly are Google RESTRICTED scopes: requesting them keeps the OAuth client out... (see source: core/app-config/src/main/resources/application.yml:1976) |
app.mcp.oauth.providers.google-drive.client-secret |
${MCP_OAUTH_GOOGLE_CLIENT_SECRET:} |
all shapes | Group app.mcp.oauth.providers.google-drive: drive.readonly and gmail.readonly are Google RESTRICTED scopes: requesting them keeps the OAuth client out... (see source: core/app-config/src/main/resources/application.yml:1977) |
app.mcp.oauth.providers.google-drive.enabled |
${MCP_OAUTH_GOOGLE_DRIVE_ENABLED:false} |
all shapes | Group app.mcp.oauth.providers.google-drive: drive.readonly and gmail.readonly are Google RESTRICTED scopes: requesting them keeps the OAuth client out... (see source: core/app-config/src/main/resources/application.yml:1978) |
app.mcp.oauth.providers.google-gmail.client-id |
${MCP_OAUTH_GOOGLE_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1980 |
app.mcp.oauth.providers.google-gmail.client-secret |
${MCP_OAUTH_GOOGLE_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1981 |
app.mcp.oauth.providers.google-gmail.enabled |
${MCP_OAUTH_GOOGLE_GMAIL_ENABLED:false} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1982 |
app.mcp.oauth.providers.google-sheets.client-id |
${MCP_OAUTH_GOOGLE_CLIENT_ID:} |
all shapes | Group app.mcp.oauth.providers.google-sheets: Google: ONE Google Cloud OAuth client serves all four products (separate scope sets). (see source: core/app-config/src/main/resources/application.yml:1968) |
app.mcp.oauth.providers.google-sheets.client-secret |
${MCP_OAUTH_GOOGLE_CLIENT_SECRET:} |
all shapes | Group app.mcp.oauth.providers.google-sheets: Google: ONE Google Cloud OAuth client serves all four products (separate scope sets). (see source: core/app-config/src/main/resources/application.yml:1969) |
app.mcp.oauth.providers.google-sheets.enabled |
${MCP_OAUTH_GOOGLE_SHEETS_ENABLED:true} |
all shapes | Group app.mcp.oauth.providers.google-sheets: Google: ONE Google Cloud OAuth client serves all four products (separate scope sets). (see source: core/app-config/src/main/resources/application.yml:1970) |
app.mcp.oauth.providers.hubspot.client-id |
${MCP_OAUTH_HUBSPOT_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2004 |
app.mcp.oauth.providers.hubspot.client-secret |
${MCP_OAUTH_HUBSPOT_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2005 |
app.mcp.oauth.providers.hubspot.enabled |
${MCP_OAUTH_HUBSPOT_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2006 |
app.mcp.oauth.providers.linear.client-id |
${MCP_OAUTH_LINEAR_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2000 |
app.mcp.oauth.providers.linear.client-secret |
${MCP_OAUTH_LINEAR_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2001 |
app.mcp.oauth.providers.linear.enabled |
${MCP_OAUTH_LINEAR_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2002 |
app.mcp.oauth.providers.microsoft-graph.client-id |
${MCP_OAUTH_MICROSOFT_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2008 |
app.mcp.oauth.providers.microsoft-graph.client-secret |
${MCP_OAUTH_MICROSOFT_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2009 |
app.mcp.oauth.providers.microsoft-graph.enabled |
${MCP_OAUTH_MICROSOFT_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2010 |
app.mcp.oauth.providers.notion.client-id |
${MCP_OAUTH_NOTION_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1996 |
app.mcp.oauth.providers.notion.client-secret |
${MCP_OAUTH_NOTION_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1997 |
app.mcp.oauth.providers.notion.enabled |
${MCP_OAUTH_NOTION_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1998 |
app.mcp.oauth.providers.slack.client-id |
${MCP_OAUTH_SLACK_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1992 |
app.mcp.oauth.providers.slack.client-secret |
${MCP_OAUTH_SLACK_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1993 |
app.mcp.oauth.providers.slack.enabled |
${MCP_OAUTH_SLACK_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:1994 |
app.mcp.oauth.providers.zoom.client-id |
${MCP_OAUTH_ZOOM_CLIENT_ID:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2016 |
app.mcp.oauth.providers.zoom.client-secret |
${MCP_OAUTH_ZOOM_CLIENT_SECRET:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2017 |
app.mcp.oauth.providers.zoom.enabled |
${MCP_OAUTH_ZOOM_ENABLED:true} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2018 |
app.mcp.oauth.redirect-uri |
${MCP_OAUTH_REDIRECT_URI:} |
all shapes | see source: core/mcp/src/main/java/com/agenticai/core/mcp/oauth/McpOAuthClientMetadata.java |
app.mcp.oauth.timeout-seconds |
10 |
all shapes | Per-request timeout and response-body cap for every discovery/token call (all are SSRF-guarded by McpRemoteUrlGuard, https-only, no redirects). |
app.mcp.package-cache-dir |
${MCP_PACKAGE_CACHE_DIR:} |
all shapes | Owner-only (rwx------) base directory for the per-registration npm/uv caches every stdio MCP spawn inside core gets (npm-cache/, uv-cache/, uv-tools/, uv-bin/ under it, one directory each; the hourly orphan sweep... |
app.mcp.piece-host.enabled |
false |
all shapes | Group app.mcp.piece-host: Activepieces community pieces (MIT) as stdio MCP tools - piece-host/ + PieceHostCatalog. (see source: core/mcp/src/main/java/com/agenticai/core/mcp/McpCatalogService.java) |
app.mcp.piece-host.entrypoint |
/opt/piece-host/server.js |
all shapes | Group app.mcp.piece-host: Activepieces community pieces (MIT) as stdio MCP tools - piece-host/ + PieceHostCatalog. (see source: core/mcp/src/main/java/com/agenticai/core/mcp/McpCatalogService.java) |
app.mcpserver.async-tasks.max-concurrent-per-tenant |
4 |
profile mcpserver, shapes that load the module |
Max non-terminal tasks per tenant on ONE core replica (<=0 disables); start_agent_task returns a clear error beyond it. |
app.mcpserver.oauth.authorization-servers |
${APP_MCPSERVER_OAUTH_AUTHORIZATION_SERVERS:} |
profile mcpserver, shapes that load the module |
Group app.mcpserver.oauth: OAuth 2.1 option next to the vmcp_ grant keys (which keep working). (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/McpOAuthConfig.java) |
app.mcpserver.oauth.grant-claim |
mcp_grant_id |
profile mcpserver, shapes that load the module |
Group app.mcpserver.oauth: OAuth 2.1 option next to the vmcp_ grant keys (which keep working). (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/McpOAuthConfig.java) |
app.mcpserver.oauth.issuer |
${APP_MCPSERVER_OAUTH_ISSUER:} |
profile mcpserver, shapes that load the module |
Group app.mcpserver.oauth: OAuth 2.1 option next to the vmcp_ grant keys (which keep working). (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/McpOAuthConfig.java) |
app.mcpserver.oauth.jwk-set-uri |
${APP_MCPSERVER_OAUTH_JWK_SET_URI:} |
profile mcpserver, shapes that load the module |
Group app.mcpserver.oauth: OAuth 2.1 option next to the vmcp_ grant keys (which keep working). (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/McpOAuthConfig.java) |
app.mcpserver.oauth.resource |
${APP_MCPSERVER_OAUTH_RESOURCE:} |
profile mcpserver, shapes that load the module |
Group app.mcpserver.oauth: OAuth 2.1 option next to the vmcp_ grant keys (which keep working). (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/McpOAuthConfig.java) |
app.mcpserver.oauth.tenant-claim |
tenant_id |
profile mcpserver, shapes that load the module |
Group app.mcpserver.oauth: OAuth 2.1 option next to the vmcp_ grant keys (which keep working). (see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/McpOAuthConfig.java) |
app.mcpserver.rate-limit.max-calls-per-minute |
60 |
profile mcpserver, shapes that load the module |
see source: core/mcpserver/src/main/java/com/agenticai/core/mcpserver/McpServerRateLimiter.java |
app.mcpserver.rate-limit.max-task-polls-per-minute |
600 |
profile mcpserver, shapes that load the module |
get_task polls (async-tasks) use their own bucket so waiting on a long task cannot exhaust the general budget above. |
app.meta.embedded-signup.app-id |
${META_EMBEDDED_SIGNUP_APP_ID:} |
all shapes | Guided WhatsApp connection (MetaEmbeddedSignupService) - PLATFORM-level credentials, deliberately never per-tenant like the BYOK values above. |
app.meta.embedded-signup.app-secret |
${META_EMBEDDED_SIGNUP_APP_SECRET:} |
all shapes | see source: core/platform/src/main/java/com/agenticai/core/platform/reliability/DependencyHealthService.java |
app.meta.embedded-signup.config-id |
${META_EMBEDDED_SIGNUP_CONFIG_ID:} |
all shapes | Created in Meta's WhatsApp Manager under the Embedded Signup product configuration - identifies which flow/permissions FB.login requests. |
app.meta.embedded-signup.redirect-uri |
${META_EMBEDDED_SIGNUP_REDIRECT_URI:} |
all shapes | Must be a real, Meta-App-registered HTTPS redirect URI - Meta will reject the OAuth exchange otherwise (localhost does not qualify). |
app.meta.webhook-verify-token |
${META_WEBHOOK_VERIFY_TOKEN:} |
all shapes | Direct Meta WhatsApp Cloud API (WhatsAppDispatchService's alternative to Twilio). |
app.observability.langfuse.user-id-mode |
user |
all shapes | app.observability.langfuse.user-id-mode = user | user-hash | tenant (see LangfuseUserId); default user = the real Keycloak subject, as before. |
app.observability.langfuse.user-id-salt |
${LANGFUSE_USER_ID_SALT:} |
all shapes | Group app.observability.langfuse: What the Langfuse user id (span attribute langfuse.user.id) carries: user (default, unchanged) the... (see source: core/chat/src/main/java/com/agenticai/core/chat/LlmModelSupport.java) |
app.observability.tail-sampling.latency-ms |
20000 |
all shapes | Group app.observability.tail-sampling: In-process "always keep errors and slow agent runs" tail sampling (no OTel Collector needed). (see source: core/app/src/main/java/com/agenticai/core/common/tracing/TailSamplingConfiguration.java) |
app.observability.tail-sampling.max-buffered-traces |
500 |
all shapes | Group app.observability.tail-sampling: In-process "always keep errors and slow agent runs" tail sampling (no OTel Collector needed). (see source: core/app/src/main/java/com/agenticai/core/common/tracing/TailSamplingConfiguration.java) |
app.observability.tail-sampling.max-spans-per-trace |
512 |
all shapes | Group app.observability.tail-sampling: In-process "always keep errors and slow agent runs" tail sampling (no OTel Collector needed). (see source: core/app/src/main/java/com/agenticai/core/common/tracing/TailSamplingConfiguration.java) |
app.observability.tail-sampling.ttl-seconds |
300 |
all shapes | Group app.observability.tail-sampling: In-process "always keep errors and slow agent runs" tail sampling (no OTel Collector needed). (see source: core/app/src/main/java/com/agenticai/core/common/tracing/TailSamplingConfiguration.java) |
app.omnichannel.discord.base-url |
${DISCORD_GATEWAY_SERVICE_URL:} |
all shapes | Discord bot Gateway bridge: a separate Node service that is not in this repository (contract in Discord). |
app.omnichannel.discord.shared-secret |
${DISCORD_GATEWAY_SHARED_SECRET:} |
all shapes | Authenticates BOTH directions of that internal link (this app's activation-time call to the Node service, and the Node service's inbound MESSAGE_CREATE callbacks to DiscordWebhookController) - generate one with... |
app.omnichannel.inbound-executor.max-concurrent |
64 |
all shapes | Group app.omnichannel.inbound-executor: Bounded executor for the blocking LLM round trip behind inbound channel webhooks (WhatsApp QR / Meta /... (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundChannelExecutor.java) |
app.omnichannel.inbound-executor.overflow-max |
64 |
all shapes | Group app.omnichannel.inbound-executor: Bounded executor for the blocking LLM round trip behind inbound channel webhooks (WhatsApp QR / Meta /... (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundChannelExecutor.java) |
app.omnichannel.inbound-executor.overflow-per-tenant-max |
16 |
all shapes | Group app.omnichannel.inbound-executor: Bounded executor for the blocking LLM round trip behind inbound channel webhooks (WhatsApp QR / Meta /... (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundChannelExecutor.java) |
app.omnichannel.inbound-executor.per-tenant-max-concurrent |
16 |
all shapes | Group app.omnichannel.inbound-executor: Bounded executor for the blocking LLM round trip behind inbound channel webhooks (WhatsApp QR / Meta /... (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundChannelExecutor.java) |
app.omnichannel.inbound-executor.queue-capacity |
64 |
all shapes | Group app.omnichannel.inbound-executor: Bounded executor for the blocking LLM round trip behind inbound channel webhooks (WhatsApp QR / Meta /... (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundChannelExecutor.java) |
app.omnichannel.inbound-executor.queue-per-tenant |
16 |
all shapes | Group app.omnichannel.inbound-executor: Bounded executor for the blocking LLM round trip behind inbound channel webhooks (WhatsApp QR / Meta /... (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundChannelExecutor.java) |
app.omnichannel.inbound-executor.queue-wait-ms |
5000 |
all shapes | Group app.omnichannel.inbound-executor: Bounded executor for the blocking LLM round trip behind inbound channel webhooks (WhatsApp QR / Meta /... (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundChannelExecutor.java) |
app.omnichannel.inbound-executor.shed-mode |
retry |
all shapes | Group app.omnichannel.inbound-executor: Bounded executor for the blocking LLM round trip behind inbound channel webhooks (WhatsApp QR / Meta /... (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundChannelExecutor.java) |
app.omnichannel.public-base-url |
${OMNICHANNEL_PUBLIC_BASE_URL:} |
all shapes | Per-tenant default agent for inbound WhatsApp is now DB-backed (channel_default_agent table, resolved via Phase 0 Stage 4's schema routing) instead of a single global env var - see ChannelSessionService's javadoc. |
app.omnichannel.whatsapp.templates.enabled |
false |
all shapes | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/meta/WhatsAppTemplateController.java |
app.omnichannel.whatsapp.voice-notes.download-timeout-seconds |
10 |
all shapes | Group app.omnichannel.whatsapp.voice-notes: Voice notes in the sender's own language. (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/whatsappvoice/WhatsAppVoiceNoteService.java) |
app.omnichannel.whatsapp.voice-notes.enabled |
false |
all shapes | Group app.omnichannel.whatsapp.voice-notes: Voice notes in the sender's own language. (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/whatsappvoice/WhatsAppVoiceNoteService.java) |
app.omnichannel.whatsapp.voice-notes.max-bytes |
1048576 |
all shapes | Largest media file downloaded (also the in-memory buffer bound). |
app.omnichannel.whatsapp.voice-notes.max-per-sender-per-hour |
20 |
all shapes | In-memory per-sender limit (per instance, forgotten on restart); 0 = unlimited. |
app.omnichannel.whatsapp.voice-notes.max-per-tenant-per-day |
300 |
all shapes | Per-tenant ceiling on Sarvam speech-to-text calls in the last 24 h, counted from sarvam_call_log; 0 = unlimited. |
app.omnichannel.whatsapp.voice-notes.max-seconds |
60 |
all shapes | Longest note accepted. |
app.omnichannel.whatsapp.voice-notes.reply-max-chars |
500 |
all shapes | A reply longer than this is cut to a spoken summary (at a sentence end) before text-to-speech. |
app.omnichannel.whatsapp.voice-notes.reply-with-voice |
false |
all shapes | true: ALSO send the reply as a WhatsApp audio message when Sarvam has a voice for the language (11 languages; a transcribed language without a voice gets text only). |
app.omnichannel.whatsapp.voice-notes.stt-mode |
codemix |
all shapes | Group app.omnichannel.whatsapp.voice-notes: Voice notes in the sender's own language. (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/whatsappvoice/WhatsAppVoiceNoteService.java) |
app.omnichannel.whatsapp.voice-notes.stt-model |
${WHATSAPP_VOICE_NOTES_STT_MODEL:} |
all shapes | Blank model = the Sarvam client defaults. |
app.omnichannel.whatsapp.voice-notes.stt-timeout-seconds |
25 |
all shapes | Group app.omnichannel.whatsapp.voice-notes: Voice notes in the sender's own language. (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/whatsappvoice/WhatsAppVoiceNoteService.java) |
app.omnichannel.whatsapp.voice-notes.tts-codec |
mp3 |
all shapes | WhatsApp rejects WAV (Sarvam's default); mp3 is requested and the returned bytes are checked. |
app.omnichannel.whatsapp.voice-notes.tts-model |
${WHATSAPP_VOICE_NOTES_TTS_MODEL:} |
all shapes | Group app.omnichannel.whatsapp.voice-notes: Voice notes in the sender's own language. (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/whatsappvoice/WhatsAppVoiceNoteService.java) |
app.omnichannel.whatsapp.voice-notes.tts-speaker |
${WHATSAPP_VOICE_NOTES_TTS_SPEAKER:} |
all shapes | Group app.omnichannel.whatsapp.voice-notes: Voice notes in the sender's own language. (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/whatsappvoice/WhatsAppVoiceNoteService.java) |
app.omnichannel.whatsapp.voice-notes.tts-timeout-seconds |
25 |
all shapes | Group app.omnichannel.whatsapp.voice-notes: Voice notes in the sender's own language. (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/whatsappvoice/WhatsAppVoiceNoteService.java) |
app.omnichannel.whatsapp.voice-notes.verify-sha256 |
true |
all shapes | Compare the downloaded bytes with the sha256 Meta announced in the webhook. |
app.openai-compat.enabled |
false |
all shapes | Mirrors the controller's own switch; when false, /v1/** stays a plain authenticated 404 and vmcp_ keys get no special handling. |
app.payments.link-ttl-seconds |
86400 |
all shapes | E1 Agentic UPI Payments. |
app.payments.reconcile-interval-ms |
${PAYMENTS_RECONCILE_INTERVAL_MS:60000} |
all shapes | How often PaymentReconciliationJob sweeps every tenant schema for AWAITING_PAYMENT rows due for a gateway poll (the per-transaction 1m/5m/30m ladder lives on the row's next_poll_at, not here). |
app.platform.alerts.webhook-secret |
${PLATFORM_ALERTS_WEBHOOK_SECRET:} |
all shapes | Alerting infrastructure pass (2026-07) - shared secret PlatformAlertWebhookController requires on the Alertmanager webhook receiver's Authorization: Bearer header (see that controller's javadoc for why it's Bearer,... |
app.platform.master-key |
${PLATFORM_MASTER_KEY:} |
all shapes | Platform-admin "delete tenant completely" control (2026-08-07, TenantDeletionController). |
app.platform.north-star.cache-ttl-seconds |
3600 |
all shapes | Group app.platform.north-star: North-star metrics for the platform owner: GET /api/platform/metrics/north-star (platform-admin only). (see source: core/platform/src/main/java/com/agenticai/core/platform/northstar/NorthStarService.java) |
app.platform.north-star.enabled |
true |
all shapes | Group app.platform.north-star: North-star metrics for the platform owner: GET /api/platform/metrics/north-star (platform-admin only). (see source: core/platform/src/main/java/com/agenticai/core/platform/northstar/NorthStarService.java) |
app.platform.north-star.per-tenant-timeout-ms |
5000 |
all shapes | Group app.platform.north-star: North-star metrics for the platform owner: GET /api/platform/metrics/north-star (platform-admin only). (see source: core/platform/src/main/java/com/agenticai/core/platform/northstar/NorthStarQueries.java) |
app.platform.north-star.total-budget-seconds |
90 |
all shapes | Group app.platform.north-star: North-star metrics for the platform owner: GET /api/platform/metrics/north-star (platform-admin only). (see source: core/platform/src/main/java/com/agenticai/core/platform/northstar/NorthStarService.java) |
app.quality.boot-orphan-min-age |
PT2H |
all shapes | see source: core/quality/src/main/java/com/agenticai/core/quality/QualityRunOrphanSweep.java |
app.quality.boot-orphan-sweep |
all |
all shapes | app.quality.boot-orphan-sweep: all (default, fail every live row at boot, today) | stale | off. |
app.quality.boot-orphan-sweep-interval-ms |
${APP_QUALITY_BOOT_ORPHAN_SWEEP_INTERVAL_MS:600000} |
all shapes | see source: core/app-config/src/main/resources/application.yml:2214 |
app.quality.eval.calibration.kappa-threshold |
0.6 |
all shapes | A judge whose latest kappa is below this is LOW; no calibration at all is UNCALIBRATED. |
app.quality.eval.calibration.max-examples |
15 |
all shapes | Upper bound on examples per request (2 judge calls each). |
app.quality.eval.calibration.max-judge-calls |
30 |
all shapes | Hard cap on judge calls per request (each example is two). |
app.quality.eval.calibration.min-examples |
10 |
all shapes | Hard floor on examples: fewer than this says nothing (the recommended ~30 stays a warning). |
app.quality.eval.calibration.set-max-examples |
30 |
all shapes | Ceiling on items per stored set. |
app.quality.eval.calibration.set-min-examples |
20 |
all shapes | Hard floor on a stored set before it may be run: fewer items says nothing about the judge. |
app.quality.eval.code-graders.enabled |
true |
all shapes | Whether a case's deterministic code grader (V375 grader_spec) is applied during a run. |
app.quality.eval.default-mode |
full |
all shapes | How an eval run replays its cases when it names no mode and its dataset pins none: full = the production pipeline in the eval sandbox, quick = the agent's tools withheld. The platform value of the setting quality.eval.defaultMode (a workspace can override it). |
app.quality.eval.default-repeats |
3 |
all shapes | Repetitions per case when the trigger names none. The platform value of the setting quality.eval.defaultRepeats (a workspace can override it). |
app.quality.eval.sandbox.read-only-tools |
${EVAL_SANDBOX_READ_ONLY_TOOLS:} |
all shapes | Tool names or * globs the operator vouches are read-only, so they run for real in a full eval run although nobody classified them; never overrides a declared classification. |
app.quality.eval.snapshot.enabled |
true |
all shapes | Whether trigger records the agent-config snapshot (model, temperature, seed, prompt hash, tool allow-list - V375) on the run row. |
app.quality.gate.pass-hat-k.enabled |
true |
all shapes | When true, a qualifying run with repeats > 1 must also clear the pass^k bar (PassHatKGate) - the Wilson lower bound of "cases whose every repetition passed" - on top of the mean-based verdict. |
app.quality.gate.pass-hat-k.min-lower-bound |
0.5 |
all shapes | Required Wilson lower bound for the pass^k check; see PassHatKGate. |
app.quality.simulation.attack-scenarios.enabled |
${SIMULATION_ATTACK_SCENARIOS_ENABLED:true} |
all shapes | Group app.quality.simulation.attack-scenarios: "Attack my agent": built-in indirect prompt-injection scenarios (poisoned email / web page / tool... (see source: core/app-config/src/main/resources/application.yml:2270) |
app.quality.trajectory-eval.enabled |
false |
all shapes | Whether a case's grader_spec may be a trajectory spec (app.quality.trajectory-eval.enabled, default false). |
app.resilience.bulkhead.enabled |
${RESILIENCE_BULKHEAD_ENABLED:false} |
all shapes | Group app.resilience: In-house circuit breakers + bulkheads (core/tenancy com.agenticai.core.resilience). (see source: core/app-config/src/main/resources/application.yml:2845) |
app.resilience.bulkhead.max-wait |
${RESILIENCE_BULKHEAD_MAX_WAIT:0s} |
all shapes | Group app.resilience: In-house circuit breakers + bulkheads (core/tenancy com.agenticai.core.resilience). (see source: core/app-config/src/main/resources/application.yml:2846) |
app.resilience.circuit-breaker.consecutive-failure-threshold |
${RESILIENCE_CB_CONSECUTIVE_FAILURES:5} |
all shapes | Opens after this many failures IN A ROW (0 = off) ... |
app.resilience.circuit-breaker.enabled |
${RESILIENCE_CB_ENABLED:true} |
all shapes | ON by default (scale/reliability 2026-10-12): a hung hosted-LLM / sidecar / Meta / E2B / search dependency otherwise piles up request threads (and DB-holding transactions) for every tenant. |
app.resilience.circuit-breaker.failure-rate-percent |
${RESILIENCE_CB_FAILURE_RATE_PERCENT:50} |
all shapes | Group app.resilience: In-house circuit breakers + bulkheads (core/tenancy com.agenticai.core.resilience). (see source: core/app-config/src/main/resources/application.yml:2820) |
app.resilience.circuit-breaker.half-open-permitted-calls |
${RESILIENCE_CB_HALF_OPEN_CALLS:2} |
all shapes | Group app.resilience: In-house circuit breakers + bulkheads (core/tenancy com.agenticai.core.resilience). (see source: core/app-config/src/main/resources/application.yml:2826) |
app.resilience.circuit-breaker.half-open-probe-timeout |
${RESILIENCE_CB_HALF_OPEN_PROBE_TIMEOUT:30s} |
all shapes | A probe that never reports back is reclaimed after this, so a breaker cannot stick half-open. |
app.resilience.circuit-breaker.instances.hosted-llm.half-open-probe-timeout |
${RESILIENCE_CB_HOSTED_LLM_HALF_OPEN_PROBE_TIMEOUT:180s} |
all shapes | Group app.resilience.circuit-breaker.instances.hosted-llm: A hosted-LLM probe is only settled at the FIRST TOKEN, and a reasoning model can take well over 30s to get... (see source: core/app-config/src/main/resources/application.yml:2835) |
app.resilience.circuit-breaker.minimum-calls |
${RESILIENCE_CB_MINIMUM_CALLS:10} |
all shapes | Group app.resilience: In-house circuit breakers + bulkheads (core/tenancy com.agenticai.core.resilience). (see source: core/app-config/src/main/resources/application.yml:2821) |
app.resilience.circuit-breaker.open-duration |
${RESILIENCE_CB_OPEN_DURATION:30s} |
all shapes | How long an open circuit sheds before letting probe calls through. |
app.resilience.circuit-breaker.window |
${RESILIENCE_CB_WINDOW:60s} |
all shapes | Group app.resilience: In-house circuit breakers + bulkheads (core/tenancy com.agenticai.core.resilience). (see source: core/app-config/src/main/resources/application.yml:2823) |
app.resilience.circuit-breaker.window-size |
${RESILIENCE_CB_WINDOW_SIZE:20} |
all shapes | Group app.resilience: In-house circuit breakers + bulkheads (core/tenancy com.agenticai.core.resilience). (see source: core/app-config/src/main/resources/application.yml:2822) |
app.retention.purge.batch-size |
5000 |
all shapes | Group app.retention.purge: DataRetentionPurge (platform/retention): batched, advisory-locked, per-tenant purge of published... (see source: core/platform/src/main/java/com/agenticai/core/platform/retention/DataRetentionPurge.java) |
app.retention.purge.dry-run |
true |
all shapes | Group app.retention.purge: DataRetentionPurge (platform/retention): batched, advisory-locked, per-tenant purge of published... (see source: core/platform/src/main/java/com/agenticai/core/platform/retention/DataRetentionPurge.java) |
app.retention.purge.initial-delay |
${APP_RETENTION_PURGE_INITIAL_DELAY:30m} |
all shapes | Group app.retention.purge: DataRetentionPurge (platform/retention): batched, advisory-locked, per-tenant purge of published... (see source: core/app-config/src/main/resources/application.yml:523) |
app.retention.purge.max-batches-per-table |
20 |
all shapes | per table, per tenant, per tick |
app.retention.purge.max-tick-seconds |
120 |
all shapes | keep well under the advisory-lock hold budget; tenants rotate across ticks |
app.retention.purge.notification-days |
90 |
all shapes | Group app.retention.purge: DataRetentionPurge (platform/retention): batched, advisory-locked, per-tenant purge of published... (see source: core/platform/src/main/java/com/agenticai/core/platform/retention/DataRetentionPurge.java) |
app.retention.purge.pause-ms |
50 |
all shapes | yield between batches |
app.retention.purge.tool-call-log-days |
90 |
all shapes | Group app.retention.purge: DataRetentionPurge (platform/retention): batched, advisory-locked, per-tenant purge of published... (see source: core/platform/src/main/java/com/agenticai/core/platform/retention/DataRetentionPurge.java) |
app.retention.purge.tool-call-log.enabled |
false |
all shapes | Group app.retention.purge.tool-call-log: tool_call_log is opt-in: per-agent all-time call counts and the MCP-grant forensic view read it. (see source: core/platform/src/main/java/com/agenticai/core/platform/retention/DataRetentionPurge.java) |
app.retention.purge.webhook-delivery-days |
30 |
all shapes | only DELIVERED/DEAD, never PENDING |
app.sandbox.platform-e2b.enabled |
false |
all shapes | Group app.sandbox.platform-e2b: Per-tenant gating (no key): uploads, mounting and the tool all require the tenant to have an ACTIVE E2B... (see source: core/sdlc/src/main/java/com/agenticai/core/sdlc/E2bAccessResolver.java) |
app.scheduling.max-concurrent-locked-jobs |
${APP_SCHEDULING_MAX_CONCURRENT_LOCKED_JOBS:4} |
all shapes | Soft cap on @Scheduled jobs wrapped in PgAdvisoryJobLock that may hold their lock at once PER JVM. |
app.sdlc.e2b.restrict-public-traffic |
true |
all shapes | Create every E2B sandbox with network.allowPublicTraffic=false and reach it with the e2b-traffic-access-token header (https://docs.e2b.dev/network/restrict-public-access). |
app.signup.disposable-email-block.allowlist |
${SIGNUP_DISPOSABLE_EMAIL_BLOCK_ALLOWLIST:} |
all shapes | Narrow, exact-match exemption from the blocklist above - exists solely to unblock e2e/helpers/test-tenant.ts's signUpFreshTenant() fixture, which needs mailinator.com's real, Playwright-scrapable inbox for Keycloak's... |
app.signup.disposable-email-block.enabled |
true |
all shapes | Group app.signup.disposable-email-block: Disposable-email blocklist (PRICING-MONETIZATION-AUDIT-2026-07-31.md §7 item 1 / E1) -... (see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/DisposableEmailDomainService.java) |
app.signup.disposable-email-block.extra-domains |
${SIGNUP_DISPOSABLE_EMAIL_BLOCK_EXTRA_DOMAINS:} |
all shapes | Group app.signup.disposable-email-block: Disposable-email blocklist (PRICING-MONETIZATION-AUDIT-2026-07-31.md §7 item 1 / E1) -... (see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/DisposableEmailDomainService.java) |
app.signup.rate-limit.max-attempts |
5 |
all shapes | Default is 5/hour/IP (application.yml) - a full default E2E run signs up roughly 25-40 tenants from one machine, so this needs to be well above that. |
app.signup.rate-limit.window-minutes |
60 |
all shapes | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/SignupRateLimiter.java |
app.slo.api-latency-p95-ms |
1000 |
all shapes | Group app.slo: Service-level objectives the reliability console judges the platform against (the alerts are built on the... (see source: core/platform/src/main/java/com/agenticai/core/platform/reliability/SloService.java) |
app.slo.availability-target |
0.995 |
all shapes | Group app.slo: Service-level objectives the reliability console judges the platform against (the alerts are built on the... (see source: core/platform/src/main/java/com/agenticai/core/platform/reliability/SloService.java) |
app.slo.chat-ttft-p95-ms |
4000 |
all shapes | Group app.slo: Service-level objectives the reliability console judges the platform against (the alerts are built on the... (see source: core/platform/src/main/java/com/agenticai/core/platform/reliability/SloService.java) |
app.slo.min-requests |
100 |
all shapes | Group app.slo: Service-level objectives the reliability console judges the platform against (the alerts are built on the... (see source: core/platform/src/main/java/com/agenticai/core/platform/reliability/SloService.java) |
app.startup.kafka-topic-init.attempts |
3 |
all shapes | Group app.startup.kafka-topic-init: Only read when background-warmup is on: how many times the background Kafka topic creation is attempted (a... (see source: core/eventbus/src/main/java/com/agenticai/core/eventbus/WorkflowKafkaTopicBackgroundInit.java) |
app.startup.kafka-topic-init.retry-delay-seconds |
30 |
all shapes | Group app.startup.kafka-topic-init: Only read when background-warmup is on: how many times the background Kafka topic creation is attempted (a... (see source: core/eventbus/src/main/java/com/agenticai/core/eventbus/WorkflowKafkaTopicBackgroundInit.java) |
app.startup.profiling.capacity |
${APP_STARTUP_PROFILING_CAPACITY:50000} |
all shapes | Group app.startup.profiling: Spring's own start-up recorder (BufferingApplicationStartup), installed by core/app-config... (see source: core/app-config/src/main/resources/application.yml:3151) |
app.startup.profiling.dump-file |
${APP_STARTUP_PROFILING_DUMP_FILE:} |
all shapes | Group app.startup.profiling: Spring's own start-up recorder (BufferingApplicationStartup), installed by core/app-config... (see source: core/app-config/src/main/resources/application.yml:3153) |
app.startup.profiling.log-top |
${APP_STARTUP_PROFILING_LOG_TOP:25} |
all shapes | Group app.startup.profiling: Spring's own start-up recorder (BufferingApplicationStartup), installed by core/app-config... (see source: core/app-config/src/main/resources/application.yml:3152) |
app.storage.azure.connection-string |
${STORAGE_AZURE_CONNECTION_STRING:} |
all shapes | Group app.storage.azure: Settings for type=azure-blob; ignored by the other implementations. (see source: core/app-config/src/main/resources/application.yml:607) |
app.storage.bucket |
${STORAGE_BUCKET:agentic-ai} |
all shapes | Bucket every file this platform stores lives in - created automatically on startup if missing (see S3FileStorageService#ensureBucketExists). |
app.storage.secret-key |
(set in source) |
all shapes | Group app.storage: app.storage.* - the only config surface that changes between a local MinIO deployment and production AWS... (see source: core/storage/src/main/java/com/agenticai/core/storage/StorageProperties.java) |
app.system.status-cache-ttl |
10s |
all shapes | Whole-response cache for GET /api/system/status (and its api/platform/health passthrough), 2026-09-24 scale work. |
app.telegram.bot-token |
${TELEGRAM_BOT_TOKEN:} |
profile lite, profile workflow, shapes that load the module |
Group app.telegram: Copied (not moved) from application-workflow.yml - keep the two in sync. (see source: core/workflow/src/main/java/com/agenticai/core/workflow/output/TelegramOutputService.java) |
app.telegram.polling.enabled |
${TELEGRAM_POLLING_ENABLED:true} (profile lite; off elsewhere) |
profile lite |
Receive Telegram messages by long polling instead of a webhook, so a bot works with no public URL. Ignored when app.telegram.webhook-base-url is set. Run it in one process per bot: two pollers on one bot get HTTP 409. |
app.telegram.webhook-base-url |
${TELEGRAM_WEBHOOK_BASE_URL:} |
profile lite, profile workflow, shapes that load the module |
Public HTTPS base URL this backend is reachable at, used to register each tenant's Telegram webhook (TelegramCredentialService#activate -> POST https://api.telegram.org/bot/setWebhook). |
app.tenancy.migrate-skip-up-to-date |
false |
all shapes | true = the boot-time tenant-schema migration sweep skips a schema whose flyway_schema_history already lists every tenant migration on the classpath as successfully applied (one cheap query instead of a Flyway... |
app.tenant.comp.service-token |
${CORE_COMP_SERVICE_TOKEN:} |
all shapes | Academy Fast-Track Scholarship one-click fulfilment (see CORE-COMP-GRANT-CONTRACT.md). |
app.tenant.idle-skip.enabled |
false |
all shapes | Group app.tenant.idle-skip: Idle back-off for the hot short-interval tenant pollers (webhook delivery relay, knowledge ingestion,... (see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/TenantSchemaIterator.java) |
app.tenant.idle-skip.max-skip-duration |
60s |
all shapes | Group app.tenant.idle-skip: Idle back-off for the hot short-interval tenant pollers (webhook delivery relay, knowledge ingestion,... (see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/TenantSchemaIterator.java) |
app.tenant.idle-skip.max-skipped-ticks |
3 |
all shapes | Group app.tenant.idle-skip: Idle back-off for the hot short-interval tenant pollers (webhook delivery relay, knowledge ingestion,... (see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/TenantSchemaIterator.java) |
app.tenant.status-cache-ttl |
10s |
all shapes | Filter-chain hot-path cache (2026-09-24 scale work): the time TenantStatusGuardImpl may serve one tenant's isUsable answer (and the platform kill-switch answer) without re-querying. |
app.translation-job.github-api-base |
https://api.github.com |
all shapes | see source: core/platform/src/main/java/com/agenticai/core/platform/TranslationRunService.java |
app.translation-job.github-repo |
${GITHUB_REPO:} |
all shapes | 423 on-demand "Run translation job now" action (TranslationRunService). |
app.translation-job.github-token |
${GITHUB_ACTIONS_TOKEN:} |
all shapes | Classic PAT needs 'repo' + 'workflow' scope, or a fine-grained token with Actions:write on this repository. |
app.tutor.reminders.min-gap-hours |
20 |
all shapes | A member is reminded at most once per this many hours. |
app.vision.frames.free-daily |
${APP_VISION_FRAMES_FREE_DAILY:10} |
all shapes | Group app.vision: Live Vision ("Sight") - the app.vision.* config block (see application.yml): the platform-wide... (see source: core/app-config/src/main/resources/application.yml:1267) |
app.vision.frames.premium-daily |
${APP_VISION_FRAMES_PREMIUM_DAILY:500} |
all shapes | Group app.vision: Live Vision ("Sight") - the app.vision.* config block (see application.yml): the platform-wide... (see source: core/app-config/src/main/resources/application.yml:1268) |
app.vision.watch.minutes-premium-daily |
${APP_VISION_WATCH_MINUTES_PREMIUM_DAILY:120} |
all shapes | Group app.vision: Live Vision ("Sight") - the app.vision.* config block (see application.yml): the platform-wide... (see source: core/app-config/src/main/resources/application.yml:1270) |
app.workflow-events.enabled |
false |
all shapes | Defaults on for this profile, but WORKFLOW_EVENTS_ENABLED=false now really turns it off (it used to be shadowed by this literal, since profile files outrank application.yml). |
app.workflow-events.kafka.enabled |
${WORKFLOW_KAFKA_ENABLED:true} |
profile workflow |
false = this "workflow" profile starts without a broker: no KafkaAdmin topic creation, no outbox relay, no consumers. |
app.workflow-trigger.rate-limit.max-triggers |
20 |
all shapes | Direct feedback: "if I approve so many whatsapp message, then how do i control? |
app.workflow-trigger.rate-limit.window-minutes |
10 |
all shapes | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/trigger/WorkflowTriggerRateLimiter.java |
app.workflow.ai-nodes.document.max-bytes |
10485760 |
all shapes | Deployment ceilings for the document extractor; a node may set lower values, never higher ones. |
app.workflow.ai-nodes.document.max-chars |
60000 |
all shapes | Group app.workflow.ai-nodes: Dify-style AI nodes (WorkflowAiNodeHandler): question_classifier, parameter_extractor,... (see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowAiNodeHandler.java) |
app.workflow.ai-nodes.document.max-pages |
50 |
all shapes | Group app.workflow.ai-nodes: Dify-style AI nodes (WorkflowAiNodeHandler): question_classifier, parameter_extractor,... (see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowAiNodeHandler.java) |
app.workflow.ai-nodes.document.max-rows |
2000 |
all shapes | Group app.workflow.ai-nodes: Dify-style AI nodes (WorkflowAiNodeHandler): question_classifier, parameter_extractor,... (see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowAiNodeHandler.java) |
app.workflow.ai-nodes.enabled |
true |
all shapes | Group app.workflow.ai-nodes: Dify-style AI nodes (WorkflowAiNodeHandler): question_classifier, parameter_extractor,... (see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowAiNodeHandler.java) |
app.workflow.ai-nodes.max-input-chars |
12000 |
all shapes | Ceiling (chars) on the untrusted text sent to the model per call; a node may lower it, never raise it. |
app.workflow.ai-nodes.min-confidence |
0.5 |
all shapes | A classification below this confidence routes to the node's default class (a node may override it). |
app.workflow.draft-run.simulate-sends.enabled |
${WORKFLOW_DRAFT_SIMULATE_SENDS:false} |
all shapes | see source: core/app-config/src/main/resources/application.yml:3001 |
app.workflow.form-trigger.enabled |
false |
all shapes | false (default): the public form endpoints answer 404 and a Trigger node with triggerType=form fails validation. |
app.workflow.form-trigger.max-payload-chars |
20000 |
all shapes | see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowFormService.java |
app.workflow.form-trigger.max-submissions-per-minute-per-ip |
8 |
all shapes | see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowFormService.java |
app.workflow.form-trigger.max-submissions-per-minute-per-token |
30 |
all shapes | see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowFormService.java |
app.workflow.generator.enforce-approval-before-send |
true |
all shapes | On (the safe value) = WorkflowSendApprovalGuard runs on every generated graph, so a node that messages someone is never handed back without an approval step in front of it - enforced in code after generation, not... |
app.workflow.generator.max-tokens |
4096 |
all shapes | Deliberately its OWN config key, not app.agents.default-max-tokens (4096) - that default is sized for a conversational chat reply, not a full JSON workflow graph. |
app.workflow.node-error-strategy.enabled |
false |
all shapes | Per-node error strategy (onError / retry, see NodeErrorStrategy). |
app.workflow.wait-node.enabled |
false |
all shapes | false (default): a graph containing a Wait node fails validation (clear message), never silently skips it. |
app.workflow.wait-node.max-duration-days |
7 |
all shapes | Longest wait (and webhook-mode timeout) a node may ask for; a longer one is refused, not clamped. |
app.workflow.wait-node.max-resume-body-chars |
65536 |
all shapes | see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowWaitWebhookController.java |
app.workflow.wait-node.max-resume-calls-per-minute |
30 |
all shapes | Public resume URL (POST /webhook/workflow-waits/{token}) abuse limits. |
app.workflow.wait-node.sweep-initial-delay-ms |
${WORKFLOW_WAIT_SWEEP_INITIAL_DELAY_MS:60000} |
all shapes | see source: core/app-config/src/main/resources/application.yml:3034 |
app.workflow.wait-node.sweep-interval-ms |
${WORKFLOW_WAIT_SWEEP_INTERVAL_MS:30000} |
all shapes | see source: core/app-config/src/main/resources/application.yml:3033 |
app.x.oauth.redirect-uri |
${X_OAUTH_REDIRECT_URI:} |
all shapes | X (Twitter) DMs (XOAuthService) - unlike Google's platform-level client above, there is no client-id/client-secret here: X requires BYOK (each tenant's own paid-tier X Developer App), stored per-tenant via POST... |
app.x402.facilitator-timeout-ms |
${APP_X402_FACILITATOR_TIMEOUT_MS:10000} |
all shapes | Group app.x402: x402 pay-per-call seller surface (core/agents-tools tools/x402). (see source: core/app-config/src/main/resources/application.yml:817) |
app.x402.facilitator-url |
${APP_X402_FACILITATOR_URL:} |
all shapes | Optional external facilitator that broadcasts verified EIP-3009 transferWithAuthorization signatures on-chain (the repo verifies the signature locally and marks the payment VERIFIED; settlement only moves to SETTLED... |
code-intel.base-url |
${CODE_INTEL_BASE_URL:http://localhost:8090} |
all shapes | Root URL of the code-intel service, e.g. |
code-intel.enabled |
${CODE_INTEL_ENABLED:false} |
all shapes | see source: core/codeintel/src/main/java/com/agenticai/core/codeintel/CodeIntelProperties.java |
code-intel.internal-token |
${CODE_INTEL_INTERNAL_TOKEN:} |
all shapes | Shared secret sent as X-Internal-Token - must match the service's CODE_INTEL_INTERNAL_TOKEN (service run with CODE_INTEL_AUTH_MODE=internal). |
livekit.api-key |
${LIVEKIT_API_KEY:} |
all shapes | see source: core/voice/src/main/java/com/agenticai/core/voice/LiveKitTokenService.java |
livekit.api-secret |
${LIVEKIT_API_SECRET:} |
all shapes | see source: core/voice/src/main/java/com/agenticai/core/voice/LiveKitTokenService.java |
livekit.token-ttl-seconds |
3600 |
all shapes | see source: core/voice/src/main/java/com/agenticai/core/voice/LiveKitTokenService.java |
livekit.url |
ws://localhost:7880 |
all shapes | see source: core/voice/src/main/java/com/agenticai/core/voice/LiveKitTokenService.java |
management.endpoint.health.group.readiness.include |
readinessState,tenantMigration |
all shapes | Group management.endpoint.health.group: Readiness also waits for this JVM's existing tenant schemas to be migrated... (see source: core/app-config/src/main/resources/application.yml:3256) |
management.endpoint.health.probes.enabled |
true |
all shapes | Group management.endpoint.health.probes: Splits /actuator/health into liveness ("is the JVM alive") and readiness ("can it serve traffic -... (see source: core/app-config/src/main/resources/application.yml:3247) |
management.endpoint.health.show-details |
${ACTUATOR_HEALTH_SHOW_DETAILS:when-authorized} |
all shapes | Security audit 2026-09-18: was always, which handed every anonymous caller of api./actuator/health the Redis version, disk sizes and DB details. |
management.endpoint.prometheus.access |
unrestricted |
all shapes | Boot 3.4+ replaced management.endpoint..enabled with .access (ADR-0012 P1a) |
management.endpoints.web.exposure.include |
health,info,prometheus |
all shapes | prometheus added alongside health,info so Prometheus can scrape actuator/prometheus (see deploy/prometheus/prometheus.yml's "core" job and deploy/docker-compose.yml's prometheus service). |
management.health.mail.enabled |
${MANAGEMENT_HEALTH_MAIL_ENABLED:false} |
all shapes | Group management.health: The SMTP/mail health indicator is disabled on purpose. (see source: core/app-config/src/main/resources/application.yml:3267) |
management.metrics.distribution.maximum-expected-value.[agentic] |
300s |
profile latency-histograms |
see source: core/app-config/src/main/resources/application-latency-histograms.yml:25 |
management.metrics.distribution.maximum-expected-value.[http.server.requests] |
300s |
profile latency-histograms |
see source: core/app-config/src/main/resources/application-latency-histograms.yml:24 |
management.metrics.distribution.minimum-expected-value.[agentic] |
10ms |
profile latency-histograms |
see source: core/app-config/src/main/resources/application-latency-histograms.yml:22 |
management.metrics.distribution.minimum-expected-value.[http.server.requests] |
10ms |
profile latency-histograms |
see source: core/app-config/src/main/resources/application-latency-histograms.yml:21 |
management.metrics.distribution.percentiles-histogram.[agentic] |
true |
profile latency-histograms |
see source: core/app-config/src/main/resources/application-latency-histograms.yml:19 |
management.metrics.distribution.percentiles-histogram.[http.server.requests] |
true |
profile latency-histograms |
see source: core/app-config/src/main/resources/application-latency-histograms.yml:18 |
management.metrics.distribution.slo.[agentic] |
50ms,100ms,250ms,500ms,1s,2500ms,5s,10s,30s,60s,120s,300s |
profile latency-histograms |
see source: core/app-config/src/main/resources/application-latency-histograms.yml:28 |
management.metrics.distribution.slo.[http.server.requests] |
50ms,100ms,250ms,500ms,1s,2500ms,5s,10s,30s,60s,120s,300s |
profile latency-histograms |
see source: core/app-config/src/main/resources/application-latency-histograms.yml:27 |
management.otlp.tracing.headers.Authorization |
${LANGFUSE_OTEL_AUTH_HEADER:} |
all shapes | see source: core/app-config/src/main/resources/application.yml:3340 |
management.tracing.enabled |
false |
profile lite |
Group management.tracing: Latency-baseline histograms are OPT-IN: activate the latency-histograms Spring profile... (see source: core/app-config/src/main/resources/application-lite.yml:26) |
resolution.sweep.enabled |
true |
all shapes | see source: core/app/src/main/java/com/agenticai/core/common/resolution/ChannelSessionResolutionSweep.java |
server.forward-headers-strategy |
native |
all shapes | Trusted-proxy resolution of the REAL client IP, deployment-layer fix for the bug DemoSessionController#clientIp / TenantSignupController#clientIp both already documented in their own javadoc: neither trusts... |
server.port |
8080 |
all shapes | Actuator-only HTTP surface (health probes + prometheus); there are no controllers in this deployable. |
server.tomcat.remoteip.protocol-header |
X-Forwarded-Proto |
all shapes | see source: core/app-config/src/main/resources/application.yml:49 |
server.tomcat.remoteip.remote-ip-header |
X-Forwarded-For |
all shapes | see source: core/app-config/src/main/resources/application.yml:48 |
server.tomcat.remoteip.trusted-proxies |
${TRUSTED_PROXIES_REGEX:(192)\\.(168)\\.\\d\\d?\\d?\\.\\d... |
all shapes | RFC1918 private ranges, not a fixed Docker subnet: docker-compose allocates its bridge network's subnet dynamically per project/host (this deployment's happened to land on 172.18.0.0/16 - "happened to" is the... |
spring.ai.chat.client.observations.log-completion |
false |
all shapes | Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application.yml:151) |
spring.ai.chat.client.observations.log-prompt |
false |
all shapes | Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application.yml:150) |
spring.ai.chat.observations.log-completion |
false |
all shapes | Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application.yml:147) |
spring.ai.chat.observations.log-prompt |
false |
all shapes | Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application.yml:146) |
spring.ai.mcp.client.initialized |
false |
profile mcp |
Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application-mcp.yml:110) |
spring.ai.mcp.client.request-timeout |
60s |
profile mcp |
Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application-mcp.yml:111) |
spring.ai.mcp.client.stdio.connections.filesystem.command |
${MCP_FILESYSTEM_COMMAND:npx} |
profile mcp |
Windows: ProcessBuilder calls Windows' CreateProcess directly, which (unlike cmd.exe) does not resolve extensionless commands via PATHEXT. |
spring.ai.mcp.client.stdio.connections.memory.command |
${MCP_MEMORY_COMMAND:npx} |
profile mcp |
Group spring.ai.mcp.client.stdio.connections.memory: STATEFUL (one shared knowledge graph) - hidden from agents by default, see CROSS-TENANT ISOLATION above. (see source: core/app-config/src/main/resources/application-mcp.yml:137) |
spring.ai.mcp.client.stdio.connections.playwright-vision.command |
${MCP_PLAYWRIGHT_VISION_COMMAND:npx} |
profile mcp |
Group spring.ai.mcp.client.stdio.connections.playwright-vision: Master-Roadmap-2026.md item #10 ("Vision-driven browser automation upgrade") - Microsoft's official... (see source: core/app-config/src/main/resources/application-mcp.yml:162) |
spring.ai.mcp.client.stdio.connections.puppeteer.command |
${MCP_PUPPETEER_COMMAND:npx} |
profile mcp |
Group spring.ai.mcp.client.stdio.connections.puppeteer: STATEFUL (one shared browser) - hidden from agents by default, see CROSS-TENANT ISOLATION above. (see source: core/app-config/src/main/resources/application-mcp.yml:149) |
spring.ai.mcp.client.stdio.connections.sequential-thinking.command |
${MCP_SEQUENTIAL_THINKING_COMMAND:npx} |
profile mcp |
Group spring.ai.mcp.client.stdio.connections.sequential-thinking: Stateless - on the default app.mcp.static.stateless-servers allowlist. (see source: core/app-config/src/main/resources/application-mcp.yml:143) |
spring.ai.mcp.server.name |
vegaduta-mcp-server |
profile mcpserver |
Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application-mcpserver.yml:34) |
spring.ai.mcp.server.protocol |
STREAMABLE |
profile mcpserver |
Confirmed live 2026-08-15 by decompiling the pinned spring-ai-autoconfigure-mcp-server-common-1.1.7.jar: without this, McpServerAutoConfiguration$EnabledStreamableServerCondition's... |
spring.ai.mcp.server.streamable-http.mcp-endpoint |
/mcp |
profile mcpserver |
Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application-mcpserver.yml:53) |
spring.ai.mcp.server.type |
SYNC |
profile mcpserver |
Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application-mcpserver.yml:36) |
spring.ai.mcp.server.version |
1.0.0 |
profile mcpserver |
Group spring.ai: Spring AI chat observations (ChatObservationAutoConfiguration, still active). (see source: core/app-config/src/main/resources/application-mcpserver.yml:35) |
spring.application.name |
agentic-ai-core |
all shapes | see source: core/app-config/src/main/resources/application.yml:82 |
spring.data.redis.host |
${REDIS_HOST:localhost} |
all shapes | see source: core/app-config/src/main/resources/application.yml:384 |
spring.data.redis.password |
${REDIS_PASSWORD:} |
all shapes | Stage 8 hardening (infra audit, 2026-07-27) - blank default so a bare mvn spring-boot:run against a local, still-unauthenticated redis-server (no docker-compose involved) keeps working unchanged; Lettuce (Spring... |
spring.data.redis.port |
${REDIS_PORT:6379} |
all shapes | see source: core/app-config/src/main/resources/application.yml:385 |
spring.data.redis.username |
${REDIS_USERNAME:} |
all shapes | Managed Redis-protocol services (ElastiCache, Azure Cache for Redis, Memorystore) require TLS and, with ACLs, a user name. |
spring.datasource.hikari.connection-timeout |
${DB_HIKARI_CONNECTION_TIMEOUT_MS:20000} |
all shapes | Group spring.datasource.hikari: Infra audit (2026-07-27) - explicit HikariCP sizing; previously absent, so this ran on Spring Boot's bare... (see source: core/app-config/src/main/resources/application.yml:263) |
spring.datasource.hikari.maximum-pool-size |
${DB_HIKARI_MAX_POOL_SIZE:10} |
all shapes | Group spring.datasource.hikari: Infra audit (2026-07-27) - explicit HikariCP sizing; previously absent, so this ran on Spring Boot's bare... (see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/TenantMigrationService.java) |
spring.datasource.hikari.minimum-idle |
${DB_HIKARI_MIN_IDLE:5} |
all shapes | Group spring.datasource.hikari: Infra audit (2026-07-27) - explicit HikariCP sizing; previously absent, so this ran on Spring Boot's bare... (see source: core/app-config/src/main/resources/application.yml:262) |
spring.datasource.password |
${POSTGRES_PASSWORD:} |
all shapes | Required in the default password mode (app.datasource.auth, below); unused in the token modes. |
spring.datasource.username |
${POSTGRES_USER:agentic} |
all shapes | see source: core/app-config/src/main/resources/application.yml:281 |
spring.flyway.baseline-on-migrate |
false |
all shapes | see source: core/app-config/src/main/resources/application.yml:319 |
spring.flyway.enabled |
true |
all shapes | One migrator only - never race per-tenant Flyway locks from a jobs pod. |
spring.flyway.out-of-order |
true |
all shapes | Turned on after the V59 fallout from the V60/V61 renumbering below: this dev database had already applied migrations up to V65+ (from tenant/) before controlplane/V59__hosted_llm_pool_settings.sql was added (a lower... |
spring.http.client.connect-timeout |
${HTTP_CLIENT_CONNECT_TIMEOUT:10s} |
all shapes | Group spring.http: OUTBOUND HTTP TIMEOUTS (2026-07-29 security audit, §2 item 4: "grep -r timeout core/chat returns zero hits"). (see source: core/app-config/src/main/resources/application.yml:132) |
spring.http.client.read-timeout |
${HTTP_CLIENT_READ_TIMEOUT:120s} |
all shapes | Group spring.http: OUTBOUND HTTP TIMEOUTS (2026-07-29 security audit, §2 item 4: "grep -r timeout core/chat returns zero hits"). (see source: core/app-config/src/main/resources/application.yml:133) |
spring.jpa.hibernate.ddl-auto |
validate |
all shapes | Phase 0 Stage 2: Flyway (see db/migration/) now owns schema creation. |
spring.jpa.properties.hibernate.default_batch_fetch_size |
50 |
all shapes | N+1 bound: entities like Agent carry several @ElementCollection EAGER sets; without batching, loading N agents runs 6N+ extra collection queries (~800 on a 100-agent tenant). |
spring.jpa.properties.hibernate.format_sql |
false |
all shapes | see source: core/app-config/src/main/resources/application.yml:306 |
spring.kafka.consumer.auto-offset-reset |
earliest |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:80 |
spring.kafka.consumer.group-id |
agentic-ai-core |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:79 |
spring.kafka.consumer.key-deserializer |
org.apache.kafka.common.serialization.StringDeserializer |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:81 |
spring.kafka.consumer.value-deserializer |
org.apache.kafka.common.serialization.StringDeserializer |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:82 |
spring.kafka.producer.acks |
all |
profile workflow |
913/#914 (Kafka reliability hardening) - none of these five were set before, meaning the producer ran with Spring Kafka's own defaults: acks=1 (leader-only ack, a message can be lost if the leader dies before... |
spring.kafka.producer.key-serializer |
org.apache.kafka.common.serialization.StringSerializer |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:55 |
spring.kafka.producer.properties.delivery.timeout.ms |
30000 |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:71 |
spring.kafka.producer.properties.enable.idempotence |
true |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:68 |
spring.kafka.producer.properties.max.block.ms |
${WORKFLOW_KAFKA_MAX_BLOCK_MS:2000} |
profile workflow |
B6: how long KafkaProducer.send may block waiting for broker metadata. |
spring.kafka.producer.properties.max.in.flight.requests.per.connection |
5 |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:69 |
spring.kafka.producer.properties.request.timeout.ms |
10000 |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:72 |
spring.kafka.producer.properties.retries |
5 |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:70 |
spring.kafka.producer.value-serializer |
org.apache.kafka.common.serialization.StringSerializer |
profile workflow |
see source: core/app-config/src/main/resources/application-workflow.yml:56 |
spring.mail.host |
${SMTP_HOST:} |
profile lite, profile workflow, shapes that load the module |
Group spring.mail: Copied (not moved) from application-workflow.yml - keep the two in sync. (see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/email/EmailChannelAdapter.java) |
spring.mail.password |
${SMTP_PASSWORD:} |
profile lite, profile workflow |
Group spring.mail: Copied (not moved) from application-workflow.yml - keep the two in sync. (see source: core/app-config/src/main/resources/application-lite.yml:59) |
spring.mail.port |
${SMTP_PORT:587} |
profile lite, profile workflow |
Group spring.mail: Copied (not moved) from application-workflow.yml - keep the two in sync. (see source: core/app-config/src/main/resources/application-lite.yml:57) |
spring.mail.properties.mail.smtp.auth |
true |
profile lite, profile workflow |
Group spring.mail: Copied (not moved) from application-workflow.yml - keep the two in sync. (see source: core/app-config/src/main/resources/application-lite.yml:63) |
spring.mail.properties.mail.smtp.starttls.enable |
true |
profile lite, profile workflow |
Group spring.mail: Copied (not moved) from application-workflow.yml - keep the two in sync. (see source: core/app-config/src/main/resources/application-lite.yml:65) |
spring.mail.properties.mail.smtp.timeout |
${SMTP_READ_TIMEOUT_MS:60000} |
profile workflow |
Group spring.mail: Copied (not moved) from application-workflow.yml - keep the two in sync. (see source: core/app-config/src/main/resources/application-workflow.yml:104) |
spring.mail.properties.mail.smtp.writetimeout |
${SMTP_WRITE_TIMEOUT_MS:60000} |
profile workflow |
Group spring.mail: Copied (not moved) from application-workflow.yml - keep the two in sync. (see source: core/app-config/src/main/resources/application-workflow.yml:105) |
spring.servlet.multipart.max-file-size |
30MB |
profile knowledge |
Stage 6: POST /api/knowledge/upload - Spring Boot's defaults (1MB per file, 10MB per request) are tight for real .md knowledge files. |
spring.servlet.multipart.max-request-size |
30MB |
profile knowledge |
see source: core/app-config/src/main/resources/application-knowledge.yml:72 |
spring.task.scheduling.pool.size |
8 |
all shapes | Group spring.task: Section2.1/§9.3 follow-on: Spring Boot's default @Scheduled executor is a single-thread pool, and this... (see source: core/app-config/src/main/resources/application.yml:221) |
vertex.pool.enabled |
${VERTEX_ENABLED:false} |
all shapes | Master kill switch - independent of individual slot config, for pausing all Vertex traffic in one flip. |
vertex.pool.slots.location |
${VERTEX_POOL_0_LOCATION:us-central1} |
all shapes | Group vertex.pool: vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the... (see source: core/app-config/src/main/resources/application.yml:447) |
vertex.pool.slots.priority |
0 |
all shapes | Group vertex.pool: vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the... (see source: core/app-config/src/main/resources/application.yml:450) |
vertex.pool.slots.service-account-key-path |
${VERTEX_POOL_0_SA_KEY_PATH:} |
all shapes | Group vertex.pool: vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the... (see source: core/app-config/src/main/resources/application.yml:448) |
vertex.pool.slots.supports-image |
${VERTEX_POOL_0_SUPPORTS_IMAGE:true} |
all shapes | Group vertex.pool: vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the... (see source: core/app-config/src/main/resources/application.yml:452) |
vertex.pool.slots.supports-text |
${VERTEX_POOL_0_SUPPORTS_TEXT:true} |
all shapes | Group vertex.pool: vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the... (see source: core/app-config/src/main/resources/application.yml:451) |
vertex.pool.slots.supports-video |
${VERTEX_POOL_0_SUPPORTS_VIDEO:false} |
all shapes | Group vertex.pool: vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the... (see source: core/app-config/src/main/resources/application.yml:453) |
vertex.pool.slots.use-application-default-credentials |
${VERTEX_POOL_0_USE_ADC:false} |
all shapes | Group vertex.pool: vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the... (see source: core/app-config/src/main/resources/application.yml:449) |
Module app-jobs (1)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.workflow.temporal.worker-enabled |
false |
profile jobs, profile worker |
Polling the Temporal task queue is the app-worker deployable's job. |
Module billing (13)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.billing.invoice.seller |
shapes that load the module | The seller (supplier-of-record) block printed on every tax invoice - app.billing.invoice.seller.* config keys, registered via InvoiceConfiguration (same per-module @EnableConfigurationProperties pattern as... | |
app.billing.invoice.seller.address |
shapes that load the module | Group app.billing.invoice.seller: The seller (supplier-of-record) block printed on every tax invoice - app.billing.invoice.seller.* config... (see source: core/billing/src/main/java/com/agenticai/core/billing/invoice/InvoiceSellerProperties.java) |
|
app.billing.invoice.seller.gstin |
shapes that load the module | Group app.billing.invoice.seller: The seller (supplier-of-record) block printed on every tax invoice - app.billing.invoice.seller.* config... (see source: core/billing/src/main/java/com/agenticai/core/billing/invoice/InvoiceSellerProperties.java) |
|
app.billing.invoice.seller.name |
Cloudspace Learning and Development Pvt Ltd |
shapes that load the module | Group app.billing.invoice.seller: The seller (supplier-of-record) block printed on every tax invoice - app.billing.invoice.seller.* config... (see source: core/billing/src/main/java/com/agenticai/core/billing/invoice/InvoiceSellerProperties.java) |
app.billing.razorpay |
shapes that load the module | PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency), B2 (annual plans), D4 (coupons via Razorpay offer_id) - the config-backed catalog of Razorpay dashboard Plan ids per (tier, currency, period), plus the... | |
app.billing.razorpay.plans |
shapes that load the module | PRICING-MONETIZATION-AUDIT-2026-07-31 B1 (multi-currency) + B2 (annual plans): one dashboard-created Razorpay Plan id per tier x currency x billing period, bound by RazorpayPlanCatalog. | |
app.commerce.checkout.expiry-hours |
24 |
shapes that load the module | App-side expiry (see package-info's "known v0 limits") - matches the audit's proposed 24h expire_by for the gateway link. |
app.commerce.checkout.max-amount-paise |
10000000 |
shapes that load the module | UPI's per-transaction ceiling for ordinary merchant collections is Rs 1,00,000 (NPCI; higher caps exist only for specific categories), so anything above it would fail at the customer's bank anyway - better to refuse... |
app.commerce.feed.max-checkouts-per-day |
100 |
shapes that load the module | see source: core/billing/src/main/java/com/agenticai/core/billing/commerce/ProductCatalogService.java |
billing.email.dunning-from |
collections@example.com |
shapes that load the module | see source: core/billing/src/main/java/com/agenticai/core/billing/email/BillingEmailService.java |
billing.email.payment-failed-from |
billing@example.com |
shapes that load the module | see source: core/billing/src/main/java/com/agenticai/core/billing/email/BillingEmailService.java |
billing.email.receipt-from |
billing@example.com |
shapes that load the module | see source: core/billing/src/main/java/com/agenticai/core/billing/email/BillingEmailService.java |
billing.email.welcome-from |
noreply@example.com |
shapes that load the module | see source: core/billing/src/main/java/com/agenticai/core/billing/email/BillingEmailService.java |
Module brief (4)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.briefs.plan-burst-per-minute |
5 |
shapes that load the module | see source: core/brief/src/main/java/com/agenticai/core/brief/BriefRateLimiter.java |
app.briefs.plan-calls-per-day.free |
5 |
shapes that load the module | see source: core/brief/src/main/java/com/agenticai/core/brief/BriefRateLimiter.java |
app.briefs.plan-calls-per-day.premium |
50 |
shapes that load the module | see source: core/brief/src/main/java/com/agenticai/core/brief/BriefRateLimiter.java |
app.briefs.plan-calls-per-day.unlimited |
500 |
shapes that load the module | see source: core/brief/src/main/java/com/agenticai/core/brief/BriefRateLimiter.java |
Module chat (41)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.agent-turn.max-concurrent |
16 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AgentTurnService.java |
app.agent-turn.max-tokens.ceiling |
4096 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AgentTurnService.java |
app.agent-turn.max-tokens.default |
2048 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AgentTurnService.java |
app.agent-turn.temperature |
0.2 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AgentTurnService.java |
app.chat.tools.argument-validation.enabled |
true |
shapes that load the module | A tool call whose arguments are not a JSON object or miss a property the tool's schema lists as required is not run; the model gets a tool result naming the problems, the keys the tool does not know and the schema, and tries again. Property types are not checked. See source: core/chat/src/main/java/com/agenticai/core/chat/ToolArgumentValidator.java |
app.chat.tools.max-rounds-per-invocation |
0 |
shapes that load the module | Tool rounds one model invocation may run before the soft stop; 0 derives it from the call ceiling. See Tool calls: budget, rounds and result size |
app.chat.tools.repeat-guard.tools |
web_search,fetch_web_page,search_knowledge_base,list_knowledge_collections,search_conversation_history,recall_related_facts,vegaduta_api_guide,vegaduta_setup_guide |
idempotent lookups a model may not repeat verbatim inside one reply: the repeat is answered with "use the earlier result" instead of running; repeating again after that ends the loop. Comma-separated tool names, * for every tool, blank to turn it off. State-reading tools (browser, terminal, workspace, job status) are not in the default list because asking twice there is legitimate |
|
app.chat.tools.max-unknown-calls-per-invocation |
8 |
shapes that load the module | Calls to tool names the model was never offered, per model invocation, before the soft stop |
app.chat.tools.text-call-recovery.enabled |
true |
shapes that load the module | A reply that is a tool call written as text ({"name": ..., "parameters": ...}, <tool_call>, <function=...>, [TOOL_CALLS], with or without a code fence) to a tool the request offered is executed through the normal tool loop and the model is called again with the result. Replies that are not such a call are untouched. Applied to the models scope covers (below). See Run free with local models. |
app.chat.tools.text-call-recovery.scope |
WEAK_TOOL_CALLERS |
shapes that load the module | WEAK_TOOL_CALLERS: only a model known to be a weak tool caller, or one of unknown capability (see the next rows); a frontier provider and a pool row measured STRUCTURED are not wrapped and keep live streaming of a reply that starts with { or a code fence. ALL_MODELS: every model. |
app.chat.tools.text-call-recovery.providers |
OLLAMA,LOCAL_OPENAI,OPENROUTER,GROQ,META,SARVAM |
shapes that load the module | Bring-your-own providers whose models are local or open-weight and so treated as weak tool callers. Keep in step with app.chat.continuation.heuristic-providers, which uses the same idea for the unfinished-reply check. |
app.chat.tools.text-call-recovery.tool-grades |
TEXT,NONE |
shapes that load the module | A platform-hosted pool row measured at one of these tool-calling grades is a weak tool caller (so is a row in small-model mode). A row measured at a grade not listed is taken as capable. |
app.chat.tools.text-call-recovery.apply-to-unmeasured-rows |
true |
shapes that load the module | A pool row that was never measured (and is not in small-model mode) is of unknown capability: true wraps it, false leaves it unwrapped. |
app.chat.tools.text-call-recovery.max-per-invocation |
3 |
shapes that load the module | Text-form tool calls recovered in one model invocation before the reply is released as text. |
app.chat.tools.text-call-recovery.hold-back-chars |
16384 |
shapes that load the module | While streaming, how many characters of a reply that still looks like a tool call (first character {, [, a code fence or a known tag) are held back before the reply is released as text anyway. A reply that starts with a letter is never held. |
app.chat.tools.constrained-router.enabled |
true |
shapes that load the module | Constrained tool router for hosted-pool rows whose measured tool-call grade is TEXT or NONE and whose server can enforce a constraint (Ollama and llama.cpp: a JSON schema on the reply; vLLM: tool_choice=required). The model chooses {action: call|answer, tool, arguments, answer} under that constraint and the chosen call runs through the shared tool loop; rows graded STRUCTURED, and rows with the per-row override off, are untouched. See Run free with local models. |
app.chat.tools.constrained-router.max-rounds |
6 |
shapes that load the module | Tool calls the router runs in one reply before it stops and tells the user it could not finish. |
app.fim.deadline-millis |
1500 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/FimCompletionService.java |
app.fim.max-completion-chars |
2000 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/FimCompletionService.java |
app.fim.max-concurrent |
8 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/FimCompletionService.java |
app.fim.max-tokens.ceiling |
160 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/FimCompletionService.java |
app.fim.max-tokens.default |
48 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/FimCompletionService.java |
app.fim.pool-cache-millis |
60000 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/FimCompletionService.java |
app.llm.anthropic.prompt-caching.enabled |
true |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AnthropicProviderCachingStrategy.java |
app.llm.anthropic.prompt-caching.strategy |
SYSTEM_AND_TOOLS |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AnthropicProviderCachingStrategy.java |
app.llm.azure-openai.prompt-caching.enabled |
true |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AzureOpenAIProviderCachingStrategy.java |
app.llm.bedrock.api-call-attempt-timeout |
5m |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AwsBedrockAgentChatClientFactory.java |
app.llm.bedrock.api-call-timeout |
10m |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AwsBedrockAgentChatClientFactory.java |
app.llm.bedrock.client-cache.max-size |
64 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/AwsBedrockAgentChatClientFactory.java |
app.llm.gemini.byok-reasoning-effort |
low |
shapes that load the module | BYOK Gemini/Vertex agents carry no admin thinking-effort setting, and Gemini 2.5/3 think by default out of the SAME maxOutputTokens budget the reply needs - unbounded, that produced truncated or empty replies. |
app.llm.gemini.prompt-caching.enabled |
false |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/GeminiProviderCachingStrategy.java |
app.llm.genai.client-cache.idle-ttl |
30m |
shapes that load the module | BYOK Gemini/Vertex agents carry no admin thinking-effort setting, and Gemini 2.5/3 think by default out of the SAME maxOutputTokens budget the reply needs - unbounded, that produced truncated or empty replies. |
app.llm.genai.client-cache.max-size |
256 |
shapes that load the module | BYOK Gemini/Vertex agents carry no admin thinking-effort setting, and Gemini 2.5/3 think by default out of the SAME maxOutputTokens budget the reply needs - unbounded, that produced truncated or empty replies. |
app.llm.http.max-connections-per-route |
200 |
shapes that load the module | Group app.llm.http: Outbound HTTP timeouts for the LLM provider calls specifically - see LlmHttpClientFactory (core/chat). (see source: core/chat/src/main/java/com/agenticai/core/chat/LlmHttpClientFactory.java) |
app.llm.http.max-connections-total |
400 |
shapes that load the module | Group app.llm.http: Outbound HTTP timeouts for the LLM provider calls specifically - see LlmHttpClientFactory (core/chat). (see source: core/chat/src/main/java/com/agenticai/core/chat/LlmHttpClientFactory.java) |
app.llm.mistral.prompt-caching.enabled |
true |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/MistralProviderCachingStrategy.java |
app.llm.observations.enabled |
true |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/LlmModelSupport.java |
app.llm.openai.prompt-caching.enabled |
true |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/OpenAIProviderCachingStrategy.java |
app.llm.spend-counter.reconcile-max-tick-seconds |
30 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/LlmSpendCounterReconciler.java |
app.llm.stream-usage.disabled-providers |
MISTRAL |
shapes that load the module | Mistral is off by default: its API documents no stream_options field and validates request bodies strictly, so asking could 4xx every streamed turn. |
app.llm.vertex.prompt-caching.enabled |
false |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/VertexProviderCachingStrategy.java |
app.providers.sarvam.max-tokens-ceiling |
4096 |
shapes that load the module | see source: core/chat/src/main/java/com/agenticai/core/chat/SarvamAgentChatClientFactory.java |
Module codeintel (1)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
code-intel.timeout-millis |
15000 |
shapes that load the module | Per-call timeout (ms) for retrieval/graph reads. |
Module dbauth (5)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.datasource |
shapes that load the module | see source: core/dbauth/src/main/java/com/agenticai/core/dbauth/DbAuthProperties.java |
|
app.datasource.aws |
shapes that load the module | see source: core/dbauth/src/main/java/com/agenticai/core/dbauth/DbAuthProperties.java |
|
app.datasource.azure |
shapes that load the module | see source: core/dbauth/src/main/java/com/agenticai/core/dbauth/DbAuthProperties.java |
|
app.datasource.gcp |
shapes that load the module | see source: core/dbauth/src/main/java/com/agenticai/core/dbauth/DbAuthProperties.java |
|
spring.datasource.hikari |
shapes that load the module | Infra audit (2026-07-27) - explicit HikariCP sizing; previously absent, so this ran on Spring Boot's bare default (maximum-pool-size 10, minimum-idle == maximum-pool-size, connection-timeout 30000ms). |
Module devices (3)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.devices.claim.rate-limit.code-window-minutes |
10 |
shapes that load the module | see source: core/devices/src/main/java/com/agenticai/core/devices/DeviceClaimRateLimiter.java |
app.devices.claim.rate-limit.ip-window-minutes |
10 |
shapes that load the module | see source: core/devices/src/main/java/com/agenticai/core/devices/DeviceClaimRateLimiter.java |
app.devices.claim.rate-limit.max-attempts-per-code |
5 |
shapes that load the module | see source: core/devices/src/main/java/com/agenticai/core/devices/DeviceClaimRateLimiter.java |
Module documentgen (3)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.documentgen.enabled |
true |
shapes that load the module | see source: core/documentgen/src/main/java/com/agenticai/core/documentgen/service/DocumentGenerationQuotaService.java |
app.documentgen.paid-daily-cap |
50 |
shapes that load the module | see source: core/documentgen/src/main/java/com/agenticai/core/documentgen/service/DocumentGenerationQuotaService.java |
app.documentgen.paid-monthly-cap |
500 |
shapes that load the module | see source: core/documentgen/src/main/java/com/agenticai/core/documentgen/service/DocumentGenerationQuotaService.java |
Module elevenlabs (7)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.elevenlabs.voice.free-daily-cap |
5 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/VoiceQuotaService.java |
app.elevenlabs.voice.free-max-characters-per-request |
300 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/VoiceQuotaService.java |
app.elevenlabs.voice.free-monthly-cap |
50 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/VoiceQuotaService.java |
app.elevenlabs.voice.premium-daily-cap |
200 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/VoiceQuotaService.java |
app.elevenlabs.voice.premium-max-characters-per-request |
5000 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/VoiceQuotaService.java |
app.elevenlabs.voice.premium-monthly-cap |
3000 |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/VoiceQuotaService.java |
app.elevenlabs.voice.quota-enabled |
true |
shapes that load the module | see source: core/elevenlabs/src/main/java/com/agenticai/core/elevenlabs/VoiceQuotaService.java |
Module guardrails (2)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.guardrails.tenant-rules.cache-ttl-seconds |
60 |
shapes that load the module | see source: core/guardrails/src/main/java/com/agenticai/core/guardrails/TenantGuardrailRuleEvaluator.java |
app.guardrails.tenant-rules.enabled |
true |
shapes that load the module | see source: core/guardrails/src/main/java/com/agenticai/core/guardrails/TenantGuardrailRuleEvaluator.java |
Module imagegen (13)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.image.generation.enabled |
true |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/ImageGenerationQuotaService.java |
app.image.generation.free-daily-cap |
5 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/ImageGenerationQuotaService.java |
app.image.generation.free-monthly-cap |
50 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/ImageGenerationQuotaService.java |
app.image.generation.free-quality |
low |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/ImageGenerationQuotaService.java |
app.image.generation.paid-daily-cap |
50 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/ImageGenerationQuotaService.java |
app.image.generation.paid-monthly-cap |
500 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/ImageGenerationQuotaService.java |
app.image.generation.pool-model |
gemini-2.5-flash-image |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/ImageGenerationOrchestratorService.java |
app.image.generation.vertex-model |
imagen-3.0-generate-002 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/ImageGenerationOrchestratorService.java |
app.video.generation.enabled |
false |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/VideoGenerationQuotaService.java |
app.video.generation.model |
veo-3.0-fast-generate-001 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/VideoGenerationJobService.java |
app.video.generation.paid-daily-cap |
10 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/VideoGenerationQuotaService.java |
app.video.generation.paid-max-duration-seconds |
60 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/VideoGenerationQuotaService.java |
app.video.generation.paid-monthly-cap |
100 |
shapes that load the module | see source: core/imagegen/src/main/java/com/agenticai/core/imagegen/VideoGenerationQuotaService.java |
Module knowledge (7)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.embeddings |
shapes that load the module | app.embeddings.* - opt-in replacement of the Python sidecar for text embeddings. | |
app.evals.free-plan-enabled |
${EVALS_FREE_PLAN_ENABLED:true} |
shapes that load the module | 2026-10-02 research ungating (gap-analysis differentiators): opens the read-only quality surface (QualityController summary/history/ statistics/config) and the reliability endpoints (pass^k, Wilson intervals, paired... |
app.knowledge.reembed.batch-size |
64 |
shapes that load the module | see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/KnowledgeReembedService.java |
app.knowledge.vector-index.ef-search |
100 |
shapes that load the module | pgvector's default is 40; the candidate pool here is up to 50 rows AND is post-filtered by collection_id, so the default would starve the LIMIT. |
app.knowledge.vector-index.enabled |
true |
shapes that load the module | see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/VectorSchemaInitializer.java |
app.knowledge.vector-index.iterative-scan |
relaxed_order |
shapes that load the module | pgvector >= 0.8.0: keep scanning the graph when the collection_id / metadata filters discard candidates, instead of returning fewer than LIMIT rows. |
app.knowledge.vector-index.min-rows |
5000 |
shapes that load the module | see source: core/knowledge/src/main/java/com/agenticai/core/knowledge/VectorSchemaInitializer.java |
Module mcp (14)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.mcp.remote-url-denylist |
shapes that load the module | @Autowired is required: with a no-arg constructor also present Spring would pick THAT one and silently ignore app.mcp.remote-url-denylist. | |
app.mcp.stdio-launcher-allowlist |
shapes that load the module | @Autowired: with the no-arg ctor above also present, Spring would otherwise fall back to it and silently ignore app.mcp.stdio-launcher-allowlist. | |
mcp.registry |
shapes that load the module | mcp.registry.* - how core reaches the official public MCP Registry (registry.modelcontextprotocol.io) for the "Browse registry" search. | |
mcp.registry.base-url |
https://registry.modelcontextprotocol.io |
shapes that load the module | Base URL of the official registry. |
mcp.registry.cache-ttl-seconds |
120 |
shapes that load the module | How long (seconds) an identical search's result page is cached, so paging back and forth or repeat searches don't re-hit the registry on every keystroke-debounced call. |
mcp.registry.catalog-max-pages |
300 |
shapes that load the module | Safety cap on pages walked per refresh, so a broken cursor can't loop forever. |
mcp.registry.catalog-max-servers |
30000 |
shapes that load the module | Safety cap on servers held in memory. |
mcp.registry.catalog-page-size |
100 |
shapes that load the module | Page size used when walking the whole catalog in the background. |
mcp.registry.catalog-ttl-seconds |
21600 |
shapes that load the module | How long (seconds) a fetched catalog snapshot is considered fresh. |
mcp.registry.enabled |
true |
shapes that load the module | Master switch for the "Browse registry" search. |
mcp.registry.page-size |
30 |
shapes that load the module | How many results one page of the browse UI shows. |
mcp.registry.read-timeout-millis |
60000 |
shapes that load the module | Read timeout (ms) for one registry page. |
mcp.registry.timeout-millis |
8000 |
shapes that load the module | TCP connect timeout (ms). |
mcp.registry.warm-on-startup |
true |
shapes that load the module | Whether to start the first catalog fetch at application startup, so the first person to open the browse tab gets an instant, already-warm search instead of paying the cold-fetch cost themselves. |
Module omnichannel (13)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.demo.telegram.auto-register-webhook |
true |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoTelegramBot.java |
app.demo.telegram.bot-token |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoTelegramBot.java |
|
app.demo.telegram.bot-username |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoTelegramBot.java |
|
app.demo.telegram.webhook-secret |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/demo/DemoTelegramBot.java |
|
app.mail.unsubscribe-secret |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/email/EmailUnsubscribeTokenService.java |
|
app.omnichannel.dedup.ttl-minutes |
60 |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/session/InboundMessageDedupService.java |
app.omnichannel.discord.api-base |
https://discord.com/api/v10 |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/discord/DiscordApiClient.java |
app.omnichannel.meta.embedded-signup.app-id |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/meta/MetaEmbeddedSignupService.java |
|
app.omnichannel.meta.embedded-signup.app-secret |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/meta/MetaEmbeddedSignupService.java |
|
app.omnichannel.meta.embedded-signup.config-id |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/meta/MetaEmbeddedSignupService.java |
|
app.omnichannel.meta.embedded-signup.redirect-uri |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/meta/MetaEmbeddedSignupService.java |
|
app.omnichannel.meta.graph-base-url |
https://graph.facebook.com |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/meta/MetaMediaGraphApiClient.java |
app.omnichannel.slack.api-base |
https://slack.com/api |
shapes that load the module | see source: core/omnichannel/src/main/java/com/agenticai/core/omnichannel/slack/SlackApiClient.java |
Module platform (36)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
BUILD_TIME |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/ops/PlatformVersionService.java |
|
GIT_SHA |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/ops/PlatformVersionService.java |
|
abuse.alert.webhook.url |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/abuse/WebhookAbuseAlertSink.java |
|
app.abuse.throttle-store |
memory |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/abuse/AbuseThrottleService.java |
app.demo.editable-hours |
24 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.enabled |
true |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.image.enabled |
true |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.image.per-session-limit |
2 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.inactivity-timeout-hours |
24 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.magic.enabled |
true |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.magic.per-ip-runs |
1 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.magic.per-session-limit |
3 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.magic.run-cap |
3 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.max-live-sessions-per-ip |
2 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.message-limit |
20 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.rate-limit.exempt-ips |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
|
app.demo.rate-limit.max-attempts |
3 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.rate-limit.window-minutes |
1440 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.reminder-hours-before-expiry |
36 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.demo.shared-schema |
demo_shared |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxReclaimService.java |
app.demo.telegram.enabled |
true |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/DemoSandboxSettingsService.java |
app.landing-events.buffer-capacity |
10000 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/LandingEventBuffer.java |
app.landing-events.max-batch-size |
500 |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/LandingEventBuffer.java |
app.langfuse.base-url |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/ops/LangfuseApiClient.java |
|
app.platform.jobs.run-now-allowlist |
com.agenticai.core.platform.LlmUsageRollupScheduler#rollupYesterday,com.agenticai.core.tenancy.demo.DemoSessionExpiryJob#tick,com.agenticai.core.tenancy.StalePendingUpgradeCleanup#clearStalePendingUpgrades,com.agenticai.core.providers.ProviderModelCatalogService#scheduledRefresh |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/ops/ScheduledJobRegistryService.java |
app.platform.ops.academy-base-url |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/ops/PlatformVersionService.java |
|
app.platform.ops.app-builder-base-url |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/ops/PlatformVersionService.java |
|
app.platform.ops.web-admin-base-url |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/ops/PlatformVersionService.java |
|
app.platform.ops.web-base-url |
shapes that load the module | see source: core/platform/src/main/java/com/agenticai/core/platform/ops/PlatformVersionService.java |
|
app.platform.reliability.cache-ttl-seconds |
${PLATFORM_RELIABILITY_CACHE_TTL_SECONDS:30} |
shapes that load the module | Group app.platform.reliability: Reliability console (platform-admin only, read-only): GET /api/platform/health/dependencies... (see source: core/app-config/src/main/resources/application.yml:3185) |
app.platform.reliability.enabled |
${PLATFORM_RELIABILITY_ENABLED:true} |
shapes that load the module | Group app.platform.reliability: Reliability console (platform-admin only, read-only): GET /api/platform/health/dependencies... (see source: core/app-config/src/main/resources/application.yml:3184) |
app.platform.reliability.probe-timeout-ms |
${PLATFORM_RELIABILITY_PROBE_TIMEOUT_MS:2000} |
shapes that load the module | Group app.platform.reliability: Reliability console (platform-admin only, read-only): GET /api/platform/health/dependencies... (see source: core/app-config/src/main/resources/application.yml:3186) |
app.platform.reliability.slo.aggregate-via-redis |
${PLATFORM_RELIABILITY_SLO_REDIS:false} |
shapes that load the module | Group app.platform.reliability: Reliability console (platform-admin only, read-only): GET /api/platform/health/dependencies... (see source: core/app-config/src/main/resources/application.yml:3190) |
app.platform.reliability.slo.redis-publish-seconds |
${PLATFORM_RELIABILITY_SLO_REDIS_PUBLISH_SECONDS:300} |
shapes that load the module | Group app.platform.reliability: Reliability console (platform-admin only, read-only): GET /api/platform/health/dependencies... (see source: core/app-config/src/main/resources/application.yml:3191) |
app.platform.reliability.slo.retention-hours |
${PLATFORM_RELIABILITY_SLO_RETENTION_HOURS:48} |
shapes that load the module | Group app.platform.reliability: Reliability console (platform-admin only, read-only): GET /api/platform/health/dependencies... (see source: core/app-config/src/main/resources/application.yml:3189) |
app.platform.reliability.slo.sample-interval-seconds |
${PLATFORM_RELIABILITY_SLO_SAMPLE_INTERVAL_SECONDS:60} |
shapes that load the module | Group app.platform.reliability: Reliability console (platform-admin only, read-only): GET /api/platform/health/dependencies... (see source: core/app-config/src/main/resources/application.yml:3188) |
Module providers (5)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.hosted-pool.availability-cache-ms |
10000 |
shapes that load the module | see source: core/providers/src/main/java/com/agenticai/core/providers/HostedPoolAvailabilityService.java |
app.providers.rejected-cooldown-hours |
6 |
shapes that load the module | How long a credential the provider refused stays off-limits to BACKGROUND callers (#resolveBest(boolean)), counted from the refusal. |
openai.api-url |
https://api.openai.com/v1 |
shapes that load the module | see source: core/providers/src/main/java/com/agenticai/core/providers/modality/OpenAIWhisperVideoProvider.java |
vertex.pool |
shapes that load the module | vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the platform-subsidized paths that must not depend on any tenant's BYOK key: the anonymous /try sandbox and FREE-plan creative... | |
vertex.pool.slots |
shapes that load the module | Group vertex.pool: vertex.pool.* - the platform's own Vertex AI credentials (up to 3 GCP projects), used for the... (see source: core/providers/src/main/java/com/agenticai/core/providers/VertexPoolProperties.java) |
Module quality (6)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
LANGFUSE_HOST |
shapes that load the module | see source: core/quality/src/main/java/com/agenticai/core/quality/dataset/EvalDatasetService.java |
|
LANGFUSE_INIT_PROJECT_ID |
shapes that load the module | see source: core/quality/src/main/java/com/agenticai/core/quality/dataset/EvalDatasetService.java |
|
app.quality.eval.judge-budget-seconds |
120 |
shapes that load the module | Overall time budget for one judge run (seconds): a calibration or eval run stops spending past it. |
app.quality.eval.judge-call-timeout-seconds |
20 |
shapes that load the module | Hard wall-clock limit for one judge call (seconds). |
app.quality.eval.judge-model |
shapes that load the module | Explicit deployment-wide judge model (MODEL on the best provider, or PROVIDER/MODEL when the first segment names a provider). The platform value of the setting quality.judge.model (a workspace can override it). |
|
app.quality.eval.judge-preflight.enabled |
true |
shapes that load the module | Ask the judge one tiny question before the case loop and fail the run at once - "Judge model unavailable: reason" - when it cannot answer, instead of replaying every case against a dead judge. |
Module rl (11)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
rl.policy |
shapes that load the module | rl.policy.* - seeding config for PolicyStore's default arms. | |
rl.policy.model-route-arms |
shapes that load the module | The candidate model keys a MODEL_ROUTE policy explores between. | |
rl.reward |
shapes that load the module | rl.reward.* - how RewardService blends the per-source rl_reward_record components into one verifiable composite reward. | |
rl.reward.clip-max |
1.0 |
shapes that load the module | Upper clip bound for the composite reward fed to the bandit. |
rl.reward.clip-min |
0.0 |
shapes that load the module | Lower clip bound for the composite reward fed to the bandit. |
rl.reward.cost-weight |
0.2 |
shapes that load the module | Default weight for a COST-efficiency reward component. |
rl.reward.human-weight |
1.0 |
shapes that load the module | Default weight for a HUMAN reward component. |
rl.reward.judge-weight |
0.6 |
shapes that load the module | Default weight for a JUDGE (LLM-as-judge) reward component. |
rl.reward.latency-weight |
0.2 |
shapes that load the module | Default weight for a LATENCY-efficiency reward component. |
rl.reward.safety-veto-cap |
0.1 |
shapes that load the module | The hard ceiling a SAFETY violation imposes on the final reward. |
rl.reward.task-success-weight |
1.0 |
shapes that load the module | Default weight for a TASK_SUCCESS reward component. |
Module security (5)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.actuator.scrape-token |
${APP_ACTUATOR_SCRAPE_TOKEN:} |
shapes that load the module | Shared secret a scraper presents as Authorization: Bearer <token> to read /actuator/prometheus through a proxy, or directly when app.actuator.scrape-token-required is on. Empty: no token accepted. See ActuatorEdgeGuardFilter. |
app.actuator.scrape-token-required |
${APP_ACTUATOR_SCRAPE_TOKEN_REQUIRED:false} |
shapes that load the module | true: every non-public actuator path (/actuator/prometheus) needs the scrape token, even for a request with no proxy in front. Set it with a token when core is published straight onto a network (Lite or Personal with BIND_ADDR other than 127.0.0.1). Off by default because the Full stack's Prometheus scrapes core:8080 without a token. |
app.executor.platform.max-concurrency |
512 |
shapes that load the module | see source: core/security/src/main/java/com/agenticai/core/security/concurrent/PlatformExecutorConfiguration.java |
app.http.max-body-bytes |
16777216 |
shapes that load the module | Group app.http: Per-dependency outbound timeouts for clients that build their own transport (everything else is bounded by... (see source: core/security/src/main/java/com/agenticai/core/security/RequestBodySizeLimitFilter.java) |
app.http.small-body-max-bytes |
65536 |
shapes that load the module | Group app.http: Per-dependency outbound timeouts for clients that build their own transport (everything else is bounded by... (see source: core/security/src/main/java/com/agenticai/core/security/RequestBodySizeLimitFilter.java) |
app.http.small-body-paths |
/api/signup,/api/devices/claim,/api/public/landing-event |
shapes that load the module | Group app.http: Per-dependency outbound timeouts for clients that build their own transport (everything else is bounded by... (see source: core/security/src/main/java/com/agenticai/core/security/RequestBodySizeLimitFilter.java) |
Module sharing (2)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.sharing.rate-limit.max-grant-reads-per-minute |
120 |
shapes that load the module | see source: core/sharing/src/main/java/com/agenticai/core/sharing/ShareRateLimiter.java |
app.sharing.rate-limit.max-grant-writes-per-minute |
20 |
shapes that load the module | see source: core/sharing/src/main/java/com/agenticai/core/sharing/ShareRateLimiter.java |
Module storage (4)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.storage |
shapes that load the module | app.storage.* - the only config surface that changes between a local MinIO deployment and production AWS S3/Cloudflare R2. | |
app.storage.azure |
shapes that load the module | Settings for type=azure-blob; ignored by the other implementations. | |
app.storage.legacy-unprefixed-fallback |
true |
shapes that load the module | Whether S3FileStorageService#get/S3FileStorageService#exists may retry the BARE (un-tenant-prefixed) key after the tenant-scoped key misses - the compatibility path for objects written before tenant prefixing landed... |
app.storage.local |
shapes that load the module | Settings for type=local; ignored by the S3 implementation. |
Module templates (2)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.templates.max-imports-per-user-per-day |
20 |
shapes that load the module | see source: core/templates/src/main/java/com/agenticai/core/templates/TemplateImportRateLimiter.java |
app.templates.max-submissions-per-tenant-per-day |
25 |
shapes that load the module | see source: core/templates/src/main/java/com/agenticai/core/templates/CommunityTemplateService.java |
Module tenancy (33)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.account.deletion.max-auth-age |
PT15M |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/account/AccountSelfDeletionService.java |
app.branding.public-base-url |
shapes that load the module | Same override-or-derive shape as app.a2a.public-base-url: normally empty, because server.forward-headers-strategy=native already makes the request's own origin the public one behind Caddy/Container Apps. | |
app.demo.channels.mirror-retained-messages |
40 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoChannelLinkService.java |
app.demo.channels.rate-limit.max-messages |
8 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoChannelRateLimiter.java |
app.demo.channels.rate-limit.window-seconds |
60 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoChannelRateLimiter.java |
app.demo.image.max-prompt-length |
300 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoImageService.java |
app.demo.image.model |
gemini-2.5-flash-image |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoImageService.java |
app.demo.image.rate-limit.max-attempts |
6 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.image.rate-limit.window-minutes |
1440 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.music.clip-seconds |
15 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoMusicService.java |
app.demo.music.enabled |
true |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoMusicService.java |
app.demo.music.global-daily-cap |
40 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoMusicService.java |
app.demo.music.max-prompt-length |
300 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoMusicService.java |
app.demo.music.per-session-limit |
1 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoMusicService.java |
app.demo.share.chat.rate-limit.max-attempts |
20 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.share.chat.rate-limit.window-minutes |
60 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.share.report-revoke-threshold |
3 |
shapes that load the module | Same property (and same NEVER-localhost default) the transcript email and expiry reminder already build their links from. |
app.demo.share.report.rate-limit.max-attempts |
5 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.share.report.rate-limit.window-minutes |
1440 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.share.viewer-cap |
5 |
shapes that load the module | Same property (and same NEVER-localhost default) the transcript email and expiry reminder already build their links from. |
app.demo.transcript-email.max-per-session |
2 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoTranscriptEmailService.java |
app.demo.transcript-email.rate-limit.max-attempts |
6 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.transcript-email.rate-limit.window-minutes |
1440 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.video.enabled |
false |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoVideoService.java |
app.demo.video.global-daily-cap |
20 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoVideoService.java |
app.demo.video.max-duration-seconds |
15 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoVideoService.java |
app.demo.video.max-prompt-length |
300 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoVideoService.java |
app.demo.video.per-session-limit |
1 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoVideoService.java |
app.demo.video.rate-limit.max-attempts |
2 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.demo.video.rate-limit.window-minutes |
1440 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/demo/DemoSessionRateLimiter.java |
app.free-mode.cache-ms |
10000 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/FreeModeService.java |
app.smart-ai-budget.config-cache-ms |
10000 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/PlatformFeatureBudgetService.java |
app.tenancy.readiness-gate-max-seconds |
60 |
shapes that load the module | see source: core/tenancy/src/main/java/com/agenticai/core/tenancy/TenantMigrationGate.java |
Module voice (4)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
livekit.egress-url |
http://livekit:7880 |
shapes that load the module | Internal (compose-network) LiveKit address for calling its Twirp Egress API - deliberately separate from livekit.url (the BROWSER-facing ws:// address returned alongside join tokens). |
livekit.egress.enabled |
false |
shapes that load the module | OFF by default - deliberately not just an infra toggle. |
livekit.egress.file-type |
OGG |
shapes that load the module | OGG is LiveKit's own recommended container for an audio_only RoomComposite output (MP4-audio-only has had player-compatibility quirks in some of LiveKit's own examples) - overridable per deployment regardless. |
livekit.egress.object-key-prefix |
voice-recordings |
shapes that load the module | OGG is LiveKit's own recommended container for an audio_only RoomComposite output (MP4-audio-only has had player-compatibility quirks in some of LiveKit's own examples) - overridable per deployment regardless. |
Module workflow (11)¶
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.workflow.expressions.enabled |
true |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowExpressionResolver.java |
app.workflow.expressions.rate-limit-per-minute |
120 |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowExpressionRateLimiter.java |
app.workflow.expressions.strict |
false |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowExpressionResolver.java |
app.workflow.output-dedup.ttl-minutes |
10 |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/output/WorkflowOutputDedupGuard.java |
app.workflow.poll |
shapes that load the module | app.workflow.poll.* - the HTTP polling trigger's limits. | |
app.workflow.recovery.stale-after-minutes |
120 |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/WorkflowRunRecoveryJob.java |
app.workflow.run-data.ttl-days |
14 |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/RunDataCleanupJob.java |
app.workflow.share.max-runs-per-minute-per-ip |
12 |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/share/WorkflowShareService.java |
app.workflow.share.max-voice-turns-per-minute |
20 |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/share/WorkflowShareService.java |
app.workflow.share.report-revoke-threshold |
3 |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/share/WorkflowShareService.java |
app.workflow.temporal.activity-heartbeat-seconds |
10 |
shapes that load the module | see source: core/workflow/src/main/java/com/agenticai/core/workflow/temporal/WorkflowNodeActivitiesImpl.java |
| ### Further keys reconciled with the code (WP 3b) |
Keys the code reads that had no row above, added so ConfigurationDriftTest (core/app) can hold the whole key set to this document. Defaults are the code's; where a key is also in application.yml the two are equal.
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.agent-runtime.bounds.max-tool-steps.min / app.agent-runtime.bounds.max-tool-steps.max |
1 / 500 |
all shapes | Smallest and largest value a tenant, an agent or one request may set for the tool calls one turn may make (an override outside the range is clamped into it when read; the platform value is never clamped). |
app.agent-runtime.bounds.turn-timeout-seconds.min / app.agent-runtime.bounds.turn-timeout-seconds.max |
10 / 3600 |
all shapes | Smallest and largest value a tenant, an agent or one request may set for the wall-clock limit of one turn (an override outside the range is clamped into it when read; the platform value is never clamped). |
app.agent-runtime.bounds.tool-timeout-seconds.min / app.agent-runtime.bounds.tool-timeout-seconds.max |
1 / 1800 |
all shapes | Smallest and largest value a tenant, an agent or one request may set for the wall-clock limit of one tool call (an override outside the range is clamped into it when read; the platform value is never clamped). |
app.agent-runtime.bounds.tool-output-max-chars.min / app.agent-runtime.bounds.tool-output-max-chars.max |
500 / 200000 |
all shapes | Smallest and largest value a tenant, an agent or one request may set for the characters of one tool result the model sees (an override outside the range is clamped into it when read; the platform value is never clamped). |
app.agent-runtime.bounds.retrieval-top-k.min / app.agent-runtime.bounds.retrieval-top-k.max |
1 / 50 |
all shapes | Smallest and largest value a tenant, an agent or one request may set for the chunks one knowledge-search call returns (an override outside the range is clamped into it when read; the platform value is never clamped). |
app.agent-runtime.bounds.retrieval-min-score.min / app.agent-runtime.bounds.retrieval-min-score.max |
0 / 1 |
all shapes | Smallest and largest value a tenant, an agent or one request may set for the least relevance a returned chunk needs (an override outside the range is clamped into it when read; the platform value is never clamped). |
app.agent-runtime.bounds.continuation-passes.min / app.agent-runtime.bounds.continuation-passes.max |
0 / 16 |
all shapes | Smallest and largest value a tenant, an agent or one request may set for the extra passes to finish a reply that stopped at a step it announced (an override outside the range is clamped into it when read; the platform value is never clamped). |
app.llm.endpoints.anthropic.models-path / app.llm.endpoints.anthropic.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.anthropic.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.openai.models-path / app.llm.endpoints.openai.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.openai.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.grok.models-path / app.llm.endpoints.grok.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.grok.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.gemini.models-path / app.llm.endpoints.gemini.completions-path |
/v1beta/models / /v1beta/openai/chat/completions |
all shapes | Path appended to app.llm.endpoints.gemini.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.mistral.models-path / app.llm.endpoints.mistral.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.mistral.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.deepseek.models-path / app.llm.endpoints.deepseek.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.deepseek.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.groq.models-path / app.llm.endpoints.groq.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.groq.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.meta.models-path / app.llm.endpoints.meta.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.meta.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.openrouter.models-path / app.llm.endpoints.openrouter.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.openrouter.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.llm.endpoints.sarvam.models-path / app.llm.endpoints.sarvam.completions-path |
/v1/models / blank |
all shapes | Path appended to app.llm.endpoints.sarvam.base-url to list the vendor's models (also what a key is validated against), and the chat-completions path when it is not the conventional /v1/chat/completions (blank = conventional). A blank value falls back to the default; a path always starts with a slash. |
app.a2a.client.enabled |
${APP_A2A_CLIENT_ENABLED:false} |
all shapes | Master switch of the outbound A2A client (this platform calling external agents through the a2a_call_agent tool). Off by default; with an empty app.a2a.client.allowed-hosts no host is reachable even when on. |
app.agent-jobs.run-now-mode |
inline (profile shape-api: enqueue) |
all shapes | Where a freshly submitted job first runs: inline hands the row to this JVM's own worker pool, enqueue only writes the durable QUEUED row for the poller of another deployment shape. The shape-api profile sets enqueue so no agent turn runs on an interactive-chat JVM. |
app.chat.directives.artifacts.enabled |
${APP_CHAT_DIRECTIVES_ARTIFACTS_ENABLED:false} |
all shapes | Adds the Artifacts convention directive to the system prompt. Off by default because it costs prompt tokens; an agent opts in through app.chat.directives.artifacts.agent-ids or the all-agents switch. |
app.coding-harness.enabled |
${CODING_HARNESS_ENABLED:false} |
all shapes | Master switch of the coding-task harness (a sandboxed fix-and-test loop over a repository or archive). Off by default. |
app.coding-harness.network-enabled |
${CODING_HARNESS_NETWORK_ENABLED:false} |
all shapes | false: test commands run in an empty network namespace. true: they keep the sandbox network (dependency downloads) - weaker, the operator's call. |
app.crypto.kms.aws.region |
all shapes | AWS region of the KMS key named by app.crypto.kms.aws.key-id; blank = the AWS SDK default region chain. |
|
app.crypto.kms.azure.algorithm |
RSA-OAEP-256 |
all shapes | Key-wrap algorithm used with the Azure Key Vault key app.crypto.kms.azure.key-id. Each wrapped key records the algorithm it used, so changing this never strands old data. |
app.crypto.kms.gcp.endpoint |
https://cloudkms.googleapis.com |
all shapes | Cloud KMS API endpoint used with app.crypto.kms.gcp.key-name; only changed for a private-service-connect or test endpoint (a non-Google endpoint is refused unless app.crypto.kms.gcp.allow-insecure-test-endpoint is true). |
app.delegation.issuer |
${APP_DELEGATION_ISSUER:} |
all shapes | iss claim of the delegation tokens the platform issues to agents; blank = the A2A public base URL (app.a2a.public-base-url). |
app.demo.studio.max-workflows-per-session |
10 |
all shapes | Workflows one anonymous demo Studio session may create. |
app.demo.telegram.webhook-base-url |
${app.telegram.webhook-base-url:} |
all shapes | Public HTTPS base URL Telegram calls for the demo bot; falls back to app.telegram.webhook-base-url (same backend, same hostname). |
app.devices.claim.rate-limit.max-attempts-per-ip |
10 |
all shapes | Claim attempts one IP address may make inside app.devices.claim.rate-limit.ip-window-minutes before further attempts are refused (a platform setting overrides it at run time). |
app.embeddings.model |
${APP_EMBEDDINGS_MODEL:} |
all shapes | Model id sent in the request body of the OpenAI-compatible embeddings provider (for example text-embedding-3-small); the native width of the model is app.embeddings.dimensions. |
app.embeddings.ollama.document-prefix |
all shapes | Prefix put before each stored passage sent to Ollama; unset = the model's documented default, empty string = none. | |
app.encryption.key |
${APP_ENCRYPTION_KEY:} |
all shapes | Deployment secret from which stored tenant credentials are encrypted (EncryptionService); it must be identical on every core replica. Never log it. |
app.guardrails.implementation |
${GUARDRAILS_IMPLEMENTATION:semantic} |
all shapes | semantic (the default: pattern layer plus an LLM judge) or pattern (regex only, no extra model call per tool result). |
app.guardrails.tenant-rules.max-regex-steps |
5000000 |
all shapes | Upper bound on characters a tenant guardrail regex may read in one evaluation (guards against catastrophic backtracking). |
app.guardrails.tenant-rules.regex-timeout-ms |
250 |
all shapes | Wall-clock bound, in milliseconds, of one tenant guardrail regex evaluation. |
app.keycloak.allowed-azp |
agentic-ai-web,agentic-ai-platform-admin,agentic-ai-mobile,agentic-ai-app,agentic-ai-ios,agentic-ai-ide,agentic-ai-core |
all shapes | Comma-separated Keycloak client ids (azp) whose tokens core accepts. Set it to add a first-party client; setting it EMPTY switches the check off (emergency lever only). |
app.landing-events.batch-enabled |
false |
all shapes | true buffers the landing-page beacon in memory and writes it behind the request; false keeps the synchronous Redis path. |
app.langfuse.auth-header |
${LANGFUSE_OTEL_AUTH_HEADER:} |
all shapes | Authorization value the Langfuse public API client sends (Basic base64(public:secret)); defaults to the header the OTLP exporter already uses. |
app.mcp-server.expose-agents.enabled |
${APP_MCP_SERVER_EXPOSE_AGENTS_ENABLED:false} |
all shapes | Exposes agents and workflows as MCP tools on the inbound MCP server. Needs the mcpserver profile too; off by default. |
app.mcp.oauth.enabled |
${MCP_OAUTH_ENABLED:false} |
all shapes | Enables the OAuth connect flow for remote MCP servers (dynamic client registration or client-metadata document, PKCE). Off: the endpoints answer 404. |
app.mcp.oauth.store |
${MCP_OAUTH_STORE:memory} |
all shapes | Where pending OAuth state lives: memory (per JVM, single replica) or redis (required with app.replica-mode=multi). |
app.mcp.registry-route-tool-calls |
${APP_MCP_REGISTRY_ROUTE_TOOL_CALLS:false} |
all shapes | true routes a tenant MCP tool call to the replica that owns the connection (multi-replica); false runs it on the replica that took the turn. |
app.mcp.registry-tool-call-timeout |
${APP_MCP_REGISTRY_TOOL_CALL_TIMEOUT:PT60S} |
all shapes | How long a routed MCP tool call waits for the owning replica to answer. |
app.mcpserver.async-tasks.enabled |
${APP_MCPSERVER_ASYNC_TASKS_ENABLED:false} |
all shapes | Adds the long-running task tools (start_agent_task, get_task, cancel_task) to the inbound MCP server tools/list. |
app.memory.import.enabled |
${APP_MEMORY_IMPORT_ENABLED:true} |
all shapes | Lets a member import memory facts exported from another assistant. Also needs app.memory.self-service.enabled; either off answers 404. |
app.observability.genai-attrs |
${OTEL_GENAI_ATTRS:both} |
all shapes | OpenTelemetry GenAI attribute names on model-call spans: legacy (gen_ai.system), new (gen_ai.provider.name) or both. |
app.quality.eval.calibration.enabled |
${EVAL_JUDGE_CALIBRATION_ENABLED:false} |
all shapes | Enables judge calibration (two judge calls per labelled example, reports Cohen's kappa and order-swap consistency). Off by default because it costs model calls. |
app.replica-mode |
${APP_REPLICA_MODE:single} |
all shapes | single or multi. multi makes the startup safety check warn (or fail, with app.replica-mode-strict) about per-JVM state that breaks with two or more replicas. |
app.replica-mode-strict |
false |
all shapes | In multi replica mode, turns the startup warnings about unsafe per-JVM settings into a boot failure. app.replica-mode.strict is honoured as an alias. |
app.swarm.verifier.enabled |
${SWARM_VERIFIER_ENABLED:false} |
all shapes | Enables the swarm goal verifier (one extra model call checks the joined branch results and may order a repair round). Off by default. |
app.teams.service-url-allowed-hosts |
smba.trafficmanager.net,*.botframework.com,*.teams.microsoft.com,*.teams.microsoft.us,*.botframework.azure.us,*.botframework.azure.cn |
all shapes | Hosts a Microsoft Teams serviceUrl may point to before the bot token is sent to it (*.example.com matches strict subdomains, anything else is an exact host). |
app.tool-arg-policies.enabled |
${TOOL_ARG_POLICIES_ENABLED:true} |
all shapes | Enables per-agent tool argument policies (a call whose arguments break a declared constraint is refused before the tool runs). On by default since 2026-10-23 (false in personal). Off: the wrapper is never installed and a PUT answers 409. |
app.tools.classification.confirmation-gate |
${TOOL_CONFIRMATION_GATE:true} |
all shapes | Routes a classified tool that is both irreversible and consequential to the human approval gate. false opts out. |
app.tools.classification.confirmation-gate-exempt-tools |
${TOOL_CONFIRMATION_GATE_EXEMPT:channel_send_message,run_code,run_in_code_workspace,browser_click,browser_type,google_gmail,google_calendar} |
all shapes | Tools exempt from app.tools.classification.confirmation-gate (they have their own approval or sandbox). |
app.tools.classification.trifecta-enforcement |
${TOOL_TRIFECTA_ENFORCEMENT:warn} |
all shapes | What happens to an agent that holds private-data access, untrusted content and an outbound channel at once: off, warn, require-approval or block. |
app.tutor.enabled |
${APP_TUTOR_ENABLED:false} |
all shapes | Maps the Learn Anything tutor routes under /api/tutor. Off: every call answers 404 and the console hides the feature. |
app.tutor.reminders.enabled |
${APP_TUTOR_REMINDERS_ENABLED:false} |
all shapes | Turns on the spaced-repetition review reminders for tutor study plans (also reported by /api/tutor/status). |
app.vision.enabled |
${APP_VISION_ENABLED:true} |
all shapes | Platform-wide switch for server-side vision (snapshot frames and sessions). Off: POST /api/vision/sessions answers 503. |
app.workflow.portability.enabled |
${WORKFLOW_PORTABILITY_ENABLED:false} |
all shapes | Enables importing workflows from other tools (n8n, Dify) and exporting them. Off by default. |
app.workflow.portability.max-payload-bytes |
${WORKFLOW_PORTABILITY_MAX_PAYLOAD_BYTES:2097152} |
all shapes | Largest workflow document accepted for import, in bytes. |
openai.api-key |
all shapes | OpenAI key used by the deployment-level Whisper video transcription provider (environment variable OPENAI_API_KEY); unset, the provider bean is not created. |
|
privacy.retention.enabled |
true |
all shapes | Nightly data-retention purge. false switches the job off; tenants with no retention rule lose nothing either way. |
rl.feedback.edit-reward |
0.0 |
all shapes | Reward credited to a policy arm when the person edited the reply instead of accepting it. |
rl.feedback.thumbs-down-reward |
0.0 |
all shapes | Reward credited to a policy arm on a thumbs-down. |
rl.feedback.unresolved-reward |
0.0 |
all shapes | Reward credited to a policy arm when the person marks the conversation as not resolved. |
rl.feedback.export-undesirable-max-rating |
2 |
all shapes | Highest rating an exported training example may carry to be labelled undesirable (rl.feedback.export-desirable-min-rating is the matching lower bound). |
tool-sandbox.e2b-execution |
${TOOL_SANDBOX_E2B_EXECUTION:false} |
all shapes | Runs Python custom tools in the tenant's own E2B sandbox instead of the tool-sandbox service. Validation still uses tool-sandbox; Java tools and tools holding vault secrets still run there. |
Prompt directives per tenant (/api/prompt-directives)¶
The platform's built-in standing prompt directives (current date, memory usage, tool trust, untrusted
MCP data, tool search, context compaction, browser tools, artifacts) can be switched off or reworded
per tenant, and a free-text block can be appended for every agent. This is stored data, not
configuration: tenant-scope rows of the prompt registry (table prompt_override in each tenant schema,
migration V664; the older prompt_directive_setting of V654 was copied there and is no longer read),
changed through the console page /admin/instructions or the API below (tenant admin only). The same
texts can also be set per agent and platform-wide, with a version history, through /api/prompts (see
prompts.md). With no settings the prompt is byte-identical to the built-in one. The app.chat.directives.* flags above
stay ops kill-switches: a directive the platform does not send on a turn is never revived by a
setting.
| Call | Effect |
|---|---|
GET /api/prompt-directives |
every directive (key, title, description, securityCritical, placeholders, enabled, overridden, defaultText, overrideText, effectiveText), customInstructions (enabled, text) and assembledPreview |
PUT /api/prompt-directives/{key} body {enabled, text, acknowledgeRisk} |
switch off or reword; blank text = built-in wording; max 4000 characters; unknown key is 404 |
DELETE /api/prompt-directives/{key} |
back to the built-in wording, on |
PUT /api/prompt-directives/custom-instructions body {enabled, text} |
the free-text block, max 8000 characters, appended after the built-ins |
tool_trust and mcp_untrusted are security directives: switching them off or rewording them is
rejected with 400 unless acknowledgeRisk is true. current_date accepts the placeholder {date}
(UTC date of the turn). media_honesty and agentic_completion are retired (their rule lives in
tool_trust) and take no text. A directive with two wordings (tool_trust full and compact, context_compaction with and
without the restore tool) is two registry keys; this API writes both. The delegation and specialist-handoff
prompts resolve through the delegate agent's overrides like any other turn. Overrides are cached in-process
for app.prompts.cache.ttl (30 seconds; evicted at once on the node that handled the write), so another node
can take up to that long to see a change. Writes are audited as PROMPT_OVERRIDE_UPDATED /
PROMPT_OVERRIDE_REVERTED with the key, version, hashes and the old-to-new diff of the wording. The
assembledPreview field is illustrative only (the tenant-wide directives with a sample date); the real
prompt of an agent is POST /api/agents/{agentId}/prompt-preview.
Prompt registry (app.prompts.*)¶
Typed by PromptProperties (module prompts). The defaults below are the values in application.yml and in the class.
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.prompts.default-locale |
${APP_PROMPTS_DEFAULT_LOCALE:en} |
all shapes | Locale of the built-in prompt texts, and of a context that names none. |
app.prompts.default-max-length |
${APP_PROMPTS_DEFAULT_MAX_LENGTH:8000} |
all shapes | Longest override accepted for a key whose resource file declares no maxLength. A value below 1 falls back to 8000. |
app.prompts.chars-per-token |
${APP_PROMPTS_CHARS_PER_TOKEN:4.0} |
all shapes | Characters per token for the estimate shown next to each block of the prompt preview. A rule of thumb, not a tokenizer. A value of 0 or less falls back to 4.0. |
app.prompts.system-prompt-token-budget |
${APP_PROMPTS_SYSTEM_PROMPT_TOKEN_BUDGET:0} |
all shapes | Token budget of the standing system text; the prompt preview flags a prompt over it. 0 = no budget. |
app.prompts.history-default-limit |
${APP_PROMPTS_HISTORY_DEFAULT_LIMIT:50} |
all shapes | Rows of override history returned when the caller names no limit. |
app.prompts.history-max-limit |
${APP_PROMPTS_HISTORY_MAX_LIMIT:500} |
all shapes | Most rows of override history one call returns. |
app.prompts.list-max-limit |
${APP_PROMPTS_LIST_MAX_LIMIT:500} |
all shapes | Most rows one GET /api/prompts call returns when the caller names a limit (limit is clamped to [1, this]; offset windows the list; X-Total-Count and X-Has-More are sent). With no limit the call returns the whole registry. A value below 1 falls back to 500. |
app.prompts.cache.ttl |
${APP_PROMPTS_CACHE_TTL:30s} |
all shapes | How long a tenant's (or the platform's) stored overrides are served from memory. A write evicts the replica that handled it at once; every other replica serves its cached copy for at most this long. |
app.prompts.cache.failure-ttl |
${APP_PROMPTS_CACHE_FAILURE_TTL:5s} |
all shapes | How long the built-in defaults stand in after a failed override load, before the next attempt. |
app.prompts.cache.max-tenants |
${APP_PROMPTS_CACHE_MAX_TENANTS:10000} |
all shapes | Tenants cached before the cache is cleared. |
Settings catalog (app.settings.*)¶
The curated behaviour settings a tenant admin or an agent editor may change in the console (/api/settings/*, see settings.md) resolve agent over tenant over the platform value over the code default. The platform value of each is the property the runtime already reads (the entry names it); app.settings.* configures the catalog itself.
| Property | Default | Applies to | Meaning |
|---|---|---|---|
app.settings.cache-ttl |
${APP_SETTINGS_CACHE_TTL:10s} |
all shapes | How long a tenant's (or an agent's) stored overrides are served from memory. A write evicts the replica that handled it at once; every other replica serves its cached copy for at most this long. |
app.settings.cache-max-entries |
${APP_SETTINGS_CACHE_MAX_ENTRIES:20000} |
all shapes | (tenant, agent) override sets cached before the cache is cleared. |
app.settings.history-default-limit |
${APP_SETTINGS_HISTORY_DEFAULT_LIMIT:50} |
all shapes | Rows of setting history returned when the caller names no limit. |
app.settings.history-max-limit |
${APP_SETTINGS_HISTORY_MAX_LIMIT:200} |
all shapes | Most rows of setting history one call returns. |
app.settings.bulk-history-default-limit |
${APP_SETTINGS_BULK_HISTORY_DEFAULT_LIMIT:100} |
all shapes | Rows of the all-keys setting history (GET /api/settings/history, GET /api/agents/{id}/settings/history) returned when the caller names no limit. Capped by the next property. |
app.settings.bulk-history-max-limit |
${APP_SETTINGS_BULK_HISTORY_MAX_LIMIT:500} |
all shapes | Most rows one all-keys history call returns; a larger limit is clamped to it (page on with offset). |
app.settings.reason-max-length |
${APP_SETTINGS_REASON_MAX_LENGTH:500} |
all shapes | Longest reason a change may carry; a longer one is cut. |
app.settings.locked.<key> |
all shapes | true pins the catalog key (for example guardrails.failMode) to the platform value for every tenant and agent: the console shows it read-only with the reason, a write is a 409 SETTING_LOCKED, and a stored override is ignored. Key matching ignores case, dots, dashes and underscores. |
|
app.settings.locked-reasons.<key> |
all shapes | The text shown next to a pinned key; defaults to "Pinned by the platform operator". |
Meter-keyed histogram settings (application-latency-histograms.yml)¶
Spring Boot keys these maps by meter-name prefix (written in the YAML as slo.[agentic]). Without brackets, the properties are
management.metrics.distribution.slo.agentic, management.metrics.distribution.slo.http.server.requests, and the same two meter
prefixes under management.metrics.distribution.percentiles-histogram, management.metrics.distribution.minimum-expected-value and
management.metrics.distribution.maximum-expected-value. They are in the latency-histograms profile file; see source:
core/app-config/src/main/resources/application-latency-histograms.yml for the values.