Skip to main content

Formations

A CloudFormation-inspired declarative deployment layer that provisions an entire AI agent stack from a single JSON/YAML template.

Note: Creating a formation also creates underlying resources (agents, memories, etc.). The calling identity must also have the relevant agents:CreateAgent, memories:CreateMemory, etc. permissions.

Overview

Instead of making a dozen separate API calls to create an AI provider, memory, agent tool, and agent, you write a single template:

{
"resources": {
"MyProvider": {
"type": "ai_provider",
"properties": {
"name": "My OpenAI",
"provider": "openai",
"default_model": "gpt-4o"
}
},
"MyMemory": {
"type": "memory",
"properties": {
"name": "Product KB"
}
},
"MyAgent": {
"type": "agent",
"properties": {
"name": "Support Bot",
"ai_provider_id": { "ref": "MyProvider" },
"knowledge_config": {
"memory_ids": [{ "ref": "MyMemory" }]
}
}
}
},
"outputs": {
"agentId": { "ref": "MyAgent" }
}
}

SOAT detects that MyAgent depends on MyProvider and MyMemory through the ref expressions, creates them first, then creates the agent with the resolved physical IDs.

Data Model

Formation

FieldTypeDescription
idstringPublic ID (af_ prefix)
project_idstringProject public ID
namestringFormation name (unique per project)
templateobjectThe last applied template
outputsobjectResolved output values
statusstringcreating | active | updating | failed | deleting | deleted | delete_failed
metadataobjectArbitrary metadata
resourcesarrayResources managed by the formation
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-updated timestamp

FormationResource

FieldTypeDescription
idstringPublic ID (afr_ prefix)
logical_idstringLogical ID from the template
resource_typestringResource type (agent, tool, memory, etc.)
physical_resource_idstringPublic ID of the physical SOAT resource
statusstringpending | created | updated | deleted | failed

FormationOperation

FieldTypeDescription
idstringPublic ID (afo_ prefix)
operation_typestringcreate | update | delete
statusstringpending | running | succeeded | failed
planobjectPlanned changes computed before execution
eventsarrayPer-resource event log with timestamp, action, status
errorobjectError details if operation failed
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-updated timestamp

Key Concepts

Formation Template

A template has four top-level keys:

KeyRequiredDescription
parametersNoMap of parameter names → parameter declarations
resourcesYesMap of logical resource ID → resource declaration
outputsNoMap of output names → values (may contain ref expressions)
metadataNoArbitrary metadata stored with the formation

Parameters

Parameters make a template portable across environments by allowing deploy-time values to be injected without changing the template itself. Use the parameters key to declare them:

{
"parameters": {
"AppUrl": {
"type": "string",
"default": "https://www.example.com",
"description": "Public base URL of the application"
},
"ApiKey": {
"type": "string",
"no_echo": true,
"description": "Bearer token for API requests"
},
"SecretId": {
"type": "string",
"description": "SOAT secret ID for the AI provider"
}
},
"resources": {
"MyProvider": {
"type": "ai_provider",
"properties": {
"name": "My Provider",
"provider": "xai",
"secret_id": { "param": "SecretId" }
}
},
"MyTool": {
"type": "tool",
"properties": {
"name": "my-tool",
"execute": {
"url": { "sub": "${AppUrl}/api/endpoint" },
"headers": { "Authorization": { "sub": "Bearer ${ApiKey}" } }
}
}
}
}
}

Parameter Declaration Fields

FieldRequiredDescription
typeNoParameter type; currently only "string" is supported
defaultNoDefault value used when the parameter is not provided at deploy time
descriptionNoHuman-readable description of the parameter's purpose
no_echoNoWhen true, signals that the value is sensitive and should not be logged or displayed

Parameters without a default are required — they must be provided in the parameters field of the deploy request.

Parameter Expressions

Use these expressions anywhere in properties or outputs to reference a parameter:

ExpressionDescription
{ "param": "ParamName" }Replaced with the parameter's value as-is
{ "sub": "text ${ParamName}" }String interpolation — embeds the parameter value inside a larger string

Providing Parameter Values

Pass parameter values in the parameters field of the create or update request:

{
"project_id": "proj_xxx",
"name": "my-stack",
"template": { ... },
"parameters": {
"AppUrl": "https://staging.example.com",
"ApiKey": "sk-secret",
"SecretId": "sec_abc123"
}
}
  • Values in parameters override any default declared in the template.
  • Parameters with a default are optional in the request.
  • Parameters without a default and not provided in the request cause a 400 Missing required parameters error.
  • Parameter values are never stored in the database — provide them on every create/update call.

Providing Parameter Values via the CLI

The CLI accepts --parameter (repeatable) instead of a JSON --parameters object. It also accepts --env-file to load an .env file so that sensitive values never need to be hardcoded in the command.

Syntax options for --parameter:

SyntaxExampleWhen to use
Key=literal--parameter AppUrl=https://example.comNon-sensitive, static values
Key=$VAR or Key=${VAR}--parameter ApiKey=$API_KEYVariable already exported in the shell
Key=@VAR_NAME--parameter ApiKey=@API_KEYVariable in --env-file; shell-safe (no expansion)
KEY (no =)--parameter API_KEYRead env var by exact name from --env-file or shell env

Why $VAR breaks with --env-file

The shell expands $VAR to an empty string before the CLI process starts, so --env-file loading always arrives too late when variables are not exported in the calling shell. Use @VAR_NAME or the bare-key syntax instead — neither is interpreted by the shell.

Example — deploying with secrets from an .env file:

Given .env:

XAI_API_KEY=xai-...
TOOLS_API_KEY=tk-...
APP_URL=https://www.example.com
soat update-formation \
--formation-id af_6sBFq1eBsCwB16dM \
--template-file formation.yaml \
--env-file .env \
--parameter AppUrl=@APP_URL \
--parameter ToolsApiKey=@TOOLS_API_KEY \
--parameter XaiApiKey=@XAI_API_KEY

Or using the bare-key syntax (parameter name must match the env var name exactly):

soat update-formation \
--formation-id af_6sBFq1eBsCwB16dM \
--template-file formation.yaml \
--env-file .env \
--parameter APP_URL \
--parameter TOOLS_API_KEY \
--parameter XAI_API_KEY

Lookup order: --env-file variables are checked first; if not found there, process.env (the calling shell's exported variables) is checked. Missing variables cause the CLI to exit with an error before the API call is made.

Resource Declaration

{
"type": "agent",
"properties": { ... },
"depends_on": ["OtherLogicalId"],
"deletion_policy": "retain",
"metadata": { }
}
  • type — one of: ai_provider, tool, agent, actor, document, memory, memory_entry, webhook. See Formations Types for the full properties reference.
  • properties — resource-specific properties (snake_case, matching the REST API body fields)
  • depends_on — explicit dependency list in addition to implicit ref dependencies
  • deletion_policy — controls what happens to the physical resource when it is removed from the stack. delete (default) deletes the physical resource. retain keeps the physical resource alive and only removes the formation record.
  • metadata — arbitrary key/value stored on the resource record

Ref Expressions

Use { "ref": "LogicalId" } anywhere in a properties value (or in outputs) to substitute the physical public ID of another resource once it is created:

"ai_provider_id": { "ref": "MyProvider" }

Refs create implicit dependencies — no need to repeat them in depends_on.

Topological Ordering

SOAT builds a dependency graph from both explicit depends_on entries and implicit ref expressions, then uses topological sort (Kahn's algorithm) to determine the creation order. A template with a cycle fails validation.

Resource Lifecycle

Each resource in a formation goes through these statuses:

StatusMeaning
pendingNot yet provisioned
createdSuccessfully created by a formation deploy
updatedSuccessfully updated by a subsequent deploy
deletedDeleted when removed from the template
failedLast operation failed

The formation stack itself has these statuses:

StatusMeaning
creatingFirst deployment in progress
activeAll resources provisioned successfully
updatingA template update is in progress
failedLast deployment ended with one or more resource failures
deletingStack teardown in progress
deletedAll resources removed
delete_failedStack teardown encountered failures

Operations and Event Log

Every deploy (create, update, delete) creates a FormationOperation record with:

  • operation_typecreate | update | delete
  • statuspending | running | succeeded | failed
  • plan — the planned changes computed before execution
  • events — ordered list of per-resource events with timestamp, action, status, and error (if any)

Use GET /api/v1/formations/{formation_id}/events to retrieve the full history.

Examples

Deploy a formation

soat create-formation \
--project-id "$PROJECT_ID" \
--name "my-stack" \
--template-file formation.json

Update a formation

soat update-formation \
--formation-id af_01 \
--template-file formation.json \
--parameter AppUrl=https://staging.example.com