
Choose the providers your AI can use
Language models become a configured application capability. The application names the providers it can use; each agent selects its provider, operation and instructions.
This separates the connection to a vendor from the business task. Several agents can reuse that connection while keeping different prompts, output contracts and model choices.
Example — Give support and research different agents
A support classifier can request structured output while a research assistant holds a conversation. Both use the application’s declared providers; their behavior belongs to their own agent definitions.
For engineers
In wildo.saas.config.ts, capability enablement and runtime-scoped provider declarations are separate. This reduced selection follows Wonder Todos; merge these properties into the existing configuration. Import EngineCapability from @wildo-ai/saas-models and defineSaaSProviders from @wildo-ai/platform-config-lib. WildoDiscoveredProviderCatalog comes from the application’s generated .wildo-saas/generated/provider-catalog.types.
engineCapabilities: {
[EngineCapability.AI_LLM]: { enabled: true },
},
providers: defineSaaSProviders<WildoDiscoveredProviderCatalog>({
scopes: {
backend: {
providers: {
anthropic: {
engineCapabilities: [EngineCapability.AI_LLM],
providerCapabilities: ['LLM_CHAT'],
protocols: ['LLM_PROVIDER'],
},
},
selection: {
[EngineCapability.AI_LLM]: { primary: 'anthropic', whenUnavailable: [] },
},
},
},
}),
The deployment must also supply the selected provider’s credentials through its secret configuration. Synchronize the application configuration with wildo config sync so the backend consumes the generated provider runtime. This backend declaration does not enable a provider in the companion or frontend scopes. The agent below explicitly names anthropic; the selection policy does not rewrite that agent’s provider after a failed request.
Define an agent, then register it
The declaration below is the framework’s support-ticket example. Its agent is active, its provider and operation agree in both configuration and operation definition, and its prompt specifies the business task. The module must include it in flowsActors.agents; a file existing on disk does not register an agent.
export const ticketTriageAgent: FlowsActors_Agent = {
ref: TICKET_TRIAGE_AGENT_REF,
displayName: 'Support Ticket Triage',
status: FlowsActors_Agent_Status.ACTIVE,
config: {
providerRef: 'anthropic',
modelApplicability: LLM_Model_Applicability.STRUCTURED_AUTHORING,
operationKey: LLMProvider_OperationKey.GENERATE_STRUCTURED,
reasoningEffort: LLMProvider_ReasoningEffort.LOW,
accessControl: [{ primaryScope: ResourcePrimaryScope.APPLICATION, requiresApproval: false }],
riskLevel: ResourceOperationRiskLevel.LOW,
},
operationDefinition: {
operationKey: LLMProvider_OperationKey.GENERATE_STRUCTURED,
providerRef: 'anthropic',
inputSchema: LLMProvider_GenerateStructuredInputSchema,
outputSchema: LLMProvider_GenerateStructuredOutputSchema,
operationContextSchema: LLMProvider_OperationContext_BaseSchema,
} satisfies LLMProvider_OperationDefinitionBase,
promptSpec: {
role: 'You triage inbound customer support tickets for a SaaS product.',
persona: 'You are terse and literal. You classify only what the ticket actually says.',
skillType: FlowsActors_Agent_Skill_Type.ANALYSIS,
instructions: [
'Choose exactly one category. When a ticket spans several, pick the one the customer is asking you to ACT on.',
'Reserve "high" urgency for outages, data loss, or failed payments. Frustrated tone alone is not urgency.',
'Summarize what the customer wants, never what you would do about it.',
'Never infer facts the ticket does not state — no account ids, no product names, no dates.',
],
},
createdAt: '2026-08-01T00:00:00.000Z',
};
Provider availability and agent selection are different decisions. A provider selection list does not mean a pinned agent automatically switches vendors after a failed request. Keep providerRef aligned with the operation contract and enable the corresponding provider in the runtime scope. Per-call material belongs in invocation input, not in the durable agent prompt.
Register the declaration and define the result
The backend module contributes the agent through flowsActors. This excerpt is the registration object from the example; include it in the owning module’s existing contribution rather than replacing other registered agents:
export const moduleBackend_FlowsActorsRegistry: { agents: FlowsActors_Agent[] } = {
agents: [ticketTriageAgent],
};
The invocation below uses this application-owned response contract. The descriptions tell the model what the fields mean; parsing the returned value establishes that it has the expected shape.
enum TicketCategory {
BILLING = 'billing',
BUG = 'bug',
FEATURE_REQUEST = 'feature_request',
ACCOUNT = 'account',
OTHER = 'other',
}
enum TicketUrgency {
LOW = 'low',
MEDIUM = 'medium',
HIGH = 'high',
}
const TicketTriageSchema = z.object({
category: z.enum(TicketCategory)
.describe('The single best-fitting category for the ticket.'),
urgency: z.enum(TicketUrgency)
.describe(`How quickly a human must respond. "${TicketUrgency.HIGH}" only for outages, data loss, or billing failures.`),
summary: z.string().max(200)
.describe('One-sentence neutral summary of what the customer is asking for.'),
});
A successful call returns a category, urgency and summary. The application still decides how those values affect routing or service commitments; a valid enum is not proof that the model classified the ticket correctly.
Invoke it with a response contract
The same example calls the registered agent with its ticket schema and validates the returned object. TicketTriageSchema is the application-owned Zod output contract; the service receives AgentsBackendService through dependency injection.
@injectable()
export class TicketTriageBackendService {
constructor(
@inject(SAAS_SERVICE_TYPES.AgentsBackendService) private readonly agentsService: AgentsBackendService,
) {}
public async triageTicket(ticketBody: string): Promise<z.infer<typeof TicketTriageSchema>> {
const invocation: FlowsActors_Agent_Invocation<
typeof LLMProvider_GenerateStructuredInputSchema,
typeof LLMProvider_GenerateStructuredOutputSchema,
typeof LLMProvider_OperationContext_BaseSchema
> = {
agentRef: TICKET_TRIAGE_AGENT_REF,
initiator: {},
invocationContext: [],
input: {
prompt: `Triage this support ticket:\n\n${ticketBody}`,
schema: TicketTriageSchema,
},
output: { object: {} },
executionContext: { callTimestamp: new Date().toISOString() },
artifactBindings: [],
};
const result = await this.agentsService.invokeAgent(invocation);
return TicketTriageSchema.parse((result.output as { object: unknown }).object);
}
}