Models and Providers¶
Selecting a model¶
spring:
ai:
acp:
harness:
model: gpt-5.6-terra
on-unsupported: fail # fail | warn (default) | ignore
spring.ai.acp.harness.model is a request. When a session opens, Spring AI ACP Harness Client (SAAHC) reads the options the agent advertises and matches the request against them, in order: exact id, display name, <provider>/<model>, then a suffix match that must be unique. Only then does it send session/set_config_option. It never sends a value the agent did not advertise, because some agents (goose) accept an unknown model id and fail later, inside the turn.
When nothing matches, on-unsupported decides:
| Value | Behaviour |
|---|---|
fail |
Throws UnsupportedAcpHarnessOptionException when the session opens. Use in production when a wrong model is worse than a failed request. |
warn |
Logs once per option per runtime, naming what the agent does offer, and continues on the agent's default. |
ignore |
Continues silently. |
WARN Runtime 'goose' cannot honor model='gpt-4o-from-2024' (the agent's 'model' option offers
gpt-6-astra, gpt-5.6-luna, gpt-5.6-sol, gpt-5.6-terra, … (94 in all));
continuing with the agent's own default.
Tip
Leave model unset to use whatever the agent is already configured with, which is the one model its credentials are sure to reach. To see what an agent offers, set a deliberately wrong model with warn and read the warning, or inspect acpHarnessClient.openSession("probe").configOptions().
Override per turn with .options(o -> o.model("gpt-5.6-sol")). Options are negotiated once per session, and again only when a turn asks for something different. To see what was applied, read openSession(name).configuration() (Named sessions); each option reports a Mechanism: CONFIG_OPTION, ENDPOINT, SESSION_MODE, PROVIDERS_SET, OUT_OF_BAND, UNSUPPORTED or NOT_REQUESTED.
Provider credentials¶
An agent either already has credentials (its own login or config file), or you give it one through spring.ai.acp.harness.provider. The provider block is fixed at process start and never per request.
spring:
ai:
acp:
harness:
model: gpt-5.6-terra
provider:
id: openai # negotiated: the provider as the agent names it
api-type: openai # the API dialect; also names the env vars (OPENAI_API_KEY, …)
api-key: ${OPENAI_API_KEY} # never logged
# base-url: … # only for an endpoint of your own; see below
# headers: { X-Team: platform }
| Property | Meaning |
|---|---|
provider.id |
Which provider to use, as the agent names it. Negotiated like model; OpenCode takes it as the model prefix. |
provider.api-type |
The wire dialect. Its upper-cased form names the environment variables: openai → OPENAI_API_KEY, OPENAI_BASE_URL; azure_openai → AZURE_OPENAI_API_KEY. |
provider.api-key |
The key. Travels in the agent's environment or config file (as an {env:…} reference where the format allows), or through ACP authenticate for Codex. |
provider.base-url |
An endpoint of your own. HTTPS, or HTTP for loopback and .apps.internal only. |
provider.headers |
Extra headers for the provider endpoint. |
Any variable an adapter derives can be overridden from the runtime's tier-3 env block, which is applied last.
Bring your own endpoint¶
Point the agent at a platform gateway, a proxy, or a model server you run, with four properties and no runtime-specific block:
spring:
ai:
acp:
harness:
runtime: goose
model: deepseek-ai/DeepSeek-V4-Flash-0731
provider:
id: openai
api-type: openai
base-url: https://gateway.example.com/team-x/openai # "/v1" is added when there is no version segment
api-key: ${GENAI_API_KEY}
With base-url set, the endpoint's own /models listing decides which models exist, not the agent's built-in catalogue, and the model is applied even though the agent never advertised it (Mechanism.ENDPOINT). An endpoint that publishes no listing refuses nothing. This applies to model only; mode and provider.id are still checked against the agent.
| Runtime | How the endpoint reaches the agent |
|---|---|
| goose | OPENAI_HOST (origin and path prefix, version stripped), plus GOOSE_PROVIDER/GOOSE_MODEL at launch. The wire API is not pinned: goose chooses Responses or Chat Completions per model. |
| codex | A [model_providers.<id>] table in config.toml, the key via env_key. |
| opencode | An @ai-sdk/openai-compatible provider in opencode.json, keyed acp when id equals api-type, with that vendor's built-in provider disabled so the key cannot leak to it. |
| registry | OPENAI_BASE_URL and OPENAI_API_KEY (named from api-type), nothing more. |
Important
The endpoint has to speak the agent's dialect. codex-acp 1.12+ supports only the OpenAI Responses API, so a gateway that serves only /chat/completions fails inside Codex's first turn. Goose and OpenCode work with either. When one runtime needs a different endpoint, give it its own profile (Swapping the runtime). To pin goose to one dialect, set OPENAI_BASE_PATH in spring.ai.acp.harness.runtimes.goose.env.
Tanzu AI Models on Cloud Foundry¶
On Cloud Foundry, binding a Tanzu AI Models (GenAI) service is the whole provider configuration. At startup, SAAHC finds the binding in VCAP_SERVICES and uses its OpenAI-compatible endpoint as a bring-your-own endpoint, so every runtime reaches it with no runtime-specific settings.
- The binding sets
spring.ai.acp.harness.provider.base-urlto itsapi_baseplus the wire format its catalog names (<api_base>/openai, as java-cfenv builds it), andapi-keyto its key. It setsprovider.idandapi-typetoopenaiif they are unset. A service whose catalog names another wire format is not used. - A binding is recognised by a label starting
genaiorai-models, or agenaitag, and needs acredentials.endpointwithapi_baseandapi_key. - Without a configured
spring.ai.acp.harness.model, the first model the service's catalog (config_url) marksTOOLS-capable is chosen. If the catalog cannot be read, no model is chosen, and a warning says to set one. - Your own configuration wins, as a whole. If
spring.ai.acp.harness.providersets abase-urlorapi-key, the binding is not used, so a key is never paired with another service's endpoint. A blank value (${OPENAI_API_KEY:}with the variable unset) counts as unset, so oneapplication.yamlcan serve both a laptop and the platform. - A provider
idorapi-typeother thanopenaialso leaves the binding unused.
| Property | Default | Meaning |
|---|---|---|
spring.ai.acp.harness.tanzu-ai.enabled |
true |
Use a bound Tanzu AI Models service as the provider. |
spring.ai.acp.harness.tanzu-ai.service-name |
— | Which bound service to use when there are several; the first by default. |
Note
With goose-buildpack still supplying the goose binary, set GOOSE_TANZU_GENAI_AUTO_CONFIG=false so the buildpack's own binding logic stays out of the agent's environment.
Mode¶
spring.ai.acp.harness.mode selects the agent's session mode, and is negotiated exactly like model. Its values are the agent's own:
| Runtime | Default | Asks before acting | Option id |
|---|---|---|---|
| goose | auto |
approve, smart_approve |
mode (all values) |
| codex | agent |
plan |
collaboration_mode; sandbox presets are mode (details) |
| opencode | build |
plan |
mode; custom agents add values (details) |
Mode matters for safety: see Permissions. Other agent-specific tuning, such as reasoning effort, belongs in tier 3: spring.ai.acp.harness.runtimes.codex.config-toml.model_reasoning_effort: high.