loom.ai.config

Typed configuration for the ai: deployment section.

Parsed via ctx.section(ConfigKey.AI, AiConfig) through the existing loom.core.config loader, so ${oc.env:...} interpolations and the secret resolver apply before the decode, unchanged. Validation is fail-fast per struct and every issue carries a stable AgentErrorCode; AiConfig aggregates the issues found across every model role and endpoint and raises once.

Functions

_has_reference_shape(value)

Report whether value has the structure of a single opaque token.

_is_credentials_reference(value)

Report whether value looks like a secret reference, not a secret.

_is_loopback(hostname)

Whether hostname names this machine and nothing else.

_redact_url(url)

Strip userinfo and query so an invalid-URL message cannot leak a secret.

_url_fault(url)

Return why a remote URL is unsafe, or None when it is acceptable.

_validate_a2a_agents(agents)

Collect the issues of every ai.a2a_agents entry.

_validate_auth(component, headers_ref, auth)

Collect the issues of one endpoint's auth block.

_validate_headers_ref(component, headers_ref)

_validate_http_server(component, server)

Collect the coherence, URL and credential issues of a transport: http server.

_validate_mcp_env(component, env)

Collect the issues of a subprocess env block: valid names, single-token values.

_validate_mcp_servers(servers)

Collect the issues of every ai.mcp_servers entry.

_validate_mcp_transport(component, server)

Collect the fields of one server that its transport does not accept.

_validate_model_binding(role, target)

Collect the issues of one ai.models.<role> binding.

_validate_remote_url(component, url, build)

Collect the fault of one remote URL, redacted so it cannot leak a secret.

_validate_stdio_server(component, server)

Collect the coherence and environment issues of a transport: stdio server.

_warn_plaintext_loopback(component, url)

Announce a plaintext loopback URL, which is allowed only because it is one.

Classes

A2AAgentConfig(*, url[, headers_ref, auth])

One named remote A2A agent (ai.a2a_agents.<name>).

A2AConfig(*, base_url, expose)

A2A exposure settings (FR-041).

AgentEndpointConfig(*, enabled, auth[, ...])

Per-agent HTTP exposure opt-in (FR-029a, FR-045a).

AiConfig(*, engine, models[, specs, ...])

Deployment configuration of the AI pillar (ai: section).

McpServerConfig(*[, transport, url, ...])

One named MCP server (ai.mcp_servers.<name>).

class loom.ai.config.McpServerConfig(*, transport='http', url=None, headers_ref=None, auth=None, timeout_ms=20000, command=None, args=(), env=None)[source]

Bases: LoomFrozenStruct

One named MCP server (ai.mcp_servers.<name>).

Artifacts name a server; this is where the server lives. Keeping the transport, the address or command, the credential reference and the deadline here is what lets the same artifact move between environments unchanged.

Parameters:
transport

http for a remote endpoint, stdio for a subprocess of this worker. Each transport accepts its own fields and refuses the other’s.

Type:

str

url

https:// server URL, free of userinfo and query string. Required under http; refused under stdio.

Type:

str | None

headers_ref

Reference to headers resolved by the secrets resolver. Never a literal secret. The resolved payload must be a single Name=value header pair, a shape checked at start-up rather than here; a bearer token belongs in auth: {kind: bearer}. Mutually exclusive with auth.

Type:

str | None

auth

Named authentication strategy and its settings, flattened: kind selects an entry point registered in loom.ai.remote_auth and every other key is passed to it as a keyword argument. Mutually exclusive with headers_ref.

Type:

dict[str, str] | None

timeout_ms

Deadline of a single call to this server.

Type:

int

command

Executable that speaks MCP over its stdin/stdout. Required under stdio; refused under http.

Type:

str | None

args

Arguments passed to command.

Type:

tuple[str, …]

env

Environment variables handed to the subprocess, on top of the SDK’s safe default subset; the worker’s own environment is not inherited. Values arrive already resolved by the secrets resolver, so they must have the shape of a single token.

Type:

dict[str, str] | None

class loom.ai.config.A2AAgentConfig(*, url, headers_ref=None, auth=None)[source]

Bases: LoomFrozenStruct

One named remote A2A agent (ai.a2a_agents.<name>).

The credential is declared exactly as an MCP server declares it, and for the same reason: the artifact names the agent, the deployment says how to authenticate to it.

Parameters:
url

https:// agent URL, free of userinfo and query string.

Type:

str

headers_ref

Reference to headers resolved by the secrets resolver. Never a literal secret. The resolved payload must be a single Name=value header pair, a shape checked at start-up rather than here; a bearer token belongs in auth: {kind: bearer}. Mutually exclusive with auth.

Type:

str | None

auth

Named authentication strategy and its settings, flattened: kind selects an entry point registered in loom.ai.remote_auth and every other key is passed to it as a keyword argument. Mutually exclusive with headers_ref.

Type:

dict[str, str] | None

class loom.ai.config.AgentEndpointConfig(*, enabled, auth, allow_anonymous=False)[source]

Bases: LoomFrozenStruct

Per-agent HTTP exposure opt-in (FR-029a, FR-045a).

An agent absent from ai.endpoints is never mounted; presence requires naming the authentication explicitly — there is no default.

Parameters:
enabled

Whether to mount the agent’s HTTP endpoints.

Type:

bool

auth

Named authentication the mount requires. Mandatory.

Type:

str

allow_anonymous

Whether an empty-subject identity is accepted for this agent (FR-045a).

Type:

bool

class loom.ai.config.A2AConfig(*, base_url, expose)[source]

Bases: LoomFrozenStruct

A2A exposure settings (FR-041).

base_url

Public URL the agent card advertises.

Type:

str

expose

Agent names to publish. Must be non-empty: empty means none, never all (FR-041a).

Type:

tuple[str, …]

Raises:

AgentCompilationError – With A2A_EXPOSE_EMPTY when expose names no agent.

Parameters:
class loom.ai.config.AiConfig(*, engine, models, specs=(), skills_root=None, mcp_servers=<factory>, a2a_agents=<factory>, a2a=None, endpoints=<factory>, startup_timeout_ms=10000, remote_clients='required', max_concurrent_runs=8, max_prompt_bytes=65536, max_state_bytes=65536, health_cache_ttl_ms=5000, max_agent_depth=1)[source]

Bases: LoomFrozenStruct

Deployment configuration of the AI pillar (ai: section).

engine

Entry-point name in group loom.ai.engines.

Type:

str

specs

Glob patterns of agent artifacts, relative to the app root. Mutually exclusive with the manifest AGENTS attribute: exactly one of the two declares the artifacts of an application, and declaring both is a compilation error.

Type:

tuple[str, …]

models

Model-role bindings; must contain every role an agent declares.

Type:

dict[str, loom.ai.inference.InferenceTarget]

skills_root

Filesystem root bare skill library names resolve against.

Type:

str | None

mcp_servers

Named remote MCP servers artifacts refer to by name.

Type:

dict[str, loom.ai.config.McpServerConfig]

a2a_agents

Named remote A2A agents artifacts refer to by name.

Type:

dict[str, loom.ai.config.A2AAgentConfig]

a2a

A2A exposure; absent means no card and no A2A endpoints (FR-041).

Type:

loom.ai.config.A2AConfig | None

endpoints

Per-agent HTTP opt-in (FR-029a).

Type:

dict[str, loom.ai.config.AgentEndpointConfig]

startup_timeout_ms

Total budget of start-up: opening every live client concurrently and validating the declared tool filters share one deadline, whatever the number of servers.

Type:

int

remote_clients

Start-up tolerance for the MCP servers and A2A agents the deployment connects to, required (the default) or optional. Under optional a client that fails to connect is logged and dropped instead of aborting start-up, so an application can boot with no network. It tolerates nothing else: a missing client factory is a wiring bug and stays fatal, a server that did open and whose tool listing times out still fails start-up, and no client becomes lazy — one that never opened is not reconnected later.

Type:

str

max_concurrent_runs

Per-worker run limit (FR-033a).

Type:

int

max_prompt_bytes

Enforced while reading the request body, against the prompt field alone (FR-015).

Type:

int

max_state_bytes

Enforced while reading the request body, against the raw state bytes alone and before they are decoded — measuring the wire size is what keeps the check cheap. Joins max_prompt_bytes in the body’s own cap (FR-015); a caller’s state over this limit is refused with a 413 naming state, independently of the prompt’s own cap.

Type:

int

health_cache_ttl_ms

Refresh period of the health probe.

Type:

int

max_agent_depth

Longest chain of nested agent runs one task may open, counting the top-level run itself. A use case reaches a named agent through an Agent() marker parameter, and that handle’s run counts as one more entry in the same chain the run that reached the use case already opened. Defaults to 1, under which the top-level run alone already consumes the whole budget: an output hook, or any other use case an agent’s run invokes, that itself declares an Agent() marker finds no depth left, and the whole class of agent-calls-agent cycles is unreachable without deliberately raising this value.

Type:

int

Raises:

AgentCompilationError – Aggregating one issue per invalid model binding (incomplete provider settings, literal secret in credentials_ref), per unsafe remote server or agent (bad URL, inline credentials, out-of-range timeout), per endpoint without a named auth, for an unknown remote_clients mode, and for a max_agent_depth below 1 — which would refuse every run, including the top-level one.

Parameters: