Deploy an Agent App with Agent Formation
This tutorial shows how to use Agent Formation to deploy a complete AI agent application — including an AI provider, memory, and agent — with a single declarative template instead of many ordered API calls.
You will:
- Write a formation template that describes the desired resources.
- Validate the template to catch structural errors before deploying.
- Preview the deployment plan to see what resources will be created.
- Deploy the formation and retrieve the output IDs.
- Update the formation to change a resource property.
- Delete the formation and all its managed resources.
By the end you will understand how Agent Formation turns a multi-step SOAT workflow into one reproducible operation.
Prerequisites
- SOAT running locally. Follow the Quick Start guide to bring the stack up with Docker Compose.
- New to SOAT? Read Key Concepts to understand projects, agents, and the IAM model before diving in.
- CLI installed and configured, or SDK set up. See CLI or SDK.
- For production hardening (secrets, env vars), see Advanced Configuration.
- Server is at
http://localhost:5047. - Ollama running locally with a chat model available.
- CLI
- SDK
- curl
export SOAT_BASE_URL=http://localhost:5047
All code snippets below use a SoatClient instance created in Step 1.
import {
SoatClient,
createClient,
createConfig,
AgentFormations,
} from '@soat/sdk';
export SOAT_URL=http://localhost:5047
Step 1 — Log in as admin
Admin is the built-in superuser role. It bypasses policy evaluation entirely. See Users for full authentication details.
- CLI
- SDK
- curl
soat login-user --username admin --password Admin1234!
soat configure
const soat = new SoatClient({ baseUrl: 'http://localhost:5047' });
const { data: login } = await soat.users.loginUser({
body: { username: 'admin', password: 'Admin1234!' },
});
const ADMIN_TOKEN = login.token;
const adminSoat = new SoatClient({
baseUrl: 'http://localhost:5047',
token: ADMIN_TOKEN,
});
ADMIN_TOKEN=$(curl -s -X POST "$SOAT_URL/api/v1/users/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"Admin1234!"}' | jq -r '.token')
Step 2 — Create a project
Every resource in SOAT lives inside a project. Create one to hold the formation.
- CLI
- SDK
- curl
PROJECT_ID=$(soat create-project --name "Agent Formation Demo" | jq -r '.id')
echo "PROJECT_ID: $PROJECT_ID"
const { data: project } = await adminSoat.projects.createProject({
body: { name: 'Agent Formation Demo' },
});
const PROJECT_ID = project.id;
PROJECT_ID=$(curl -s -X POST "$SOAT_URL/api/v1/projects" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Agent Formation Demo"}' | jq -r '.id')
echo "PROJECT_ID: $PROJECT_ID"
Step 3 — Write the formation template
A formation template is a JSON object with a resources map and an optional outputs map. Each resource has a type, properties, and optional depends_on. References between resources use { "ref": "logicalId" } expressions.
This template creates a local Ollama AI provider, a memory for the agent to read from, and an agent that wires them together:
{
"resources": {
"provider": {
"type": "ai_provider",
"properties": {
"name": "Formation Ollama",
"provider": "ollama",
"default_model": "qwen2.5:0.5b"
}
},
"profileMemory": {
"type": "memory",
"properties": {
"name": "Formation Profile Memory",
"tags": ["formation", "demo"]
}
},
"assistant": {
"type": "agent",
"properties": {
"name": "Formation Assistant",
"ai_provider_id": { "ref": "provider" },
"instructions": "Answer helpfully from the knowledge base.",
"knowledge_config": {
"memory_ids": [{ "ref": "profileMemory" }],
"write_memory_id": { "ref": "profileMemory" }
}
}
}
},
"outputs": {
"agent_id": { "ref": "assistant" },
"memory_id": { "ref": "profileMemory" },
"provider_id": { "ref": "provider" }
}
}
This tutorial uses a local Ollama provider so it can run without external credentials. To connect xAI, OpenAI, Anthropic, or Amazon Bedrock instead, see Connect Third-Party LLMs.
- CLI
- curl
Store the template in a variable:
TEMPLATE=$(jq -n \
'{"resources":{"provider":{"type":"ai_provider","properties":{"name":"Formation Ollama","provider":"ollama","default_model":"qwen2.5:0.5b"}},"profileMemory":{"type":"memory","properties":{"name":"Formation Profile Memory","tags":["formation","demo"]}},"assistant":{"type":"agent","properties":{"name":"Formation Assistant","ai_provider_id":{"ref":"provider"},"instructions":"Answer helpfully from the knowledge base.","knowledge_config":{"memory_ids":[{"ref":"profileMemory"}],"write_memory_id":{"ref":"profileMemory"}}}}},"outputs":{"agent_id":{"ref":"assistant"},"memory_id":{"ref":"profileMemory"},"provider_id":{"ref":"provider"}}}')
Save this template to a file:
cat > formation.json << 'EOF'
{
"resources": {
"provider": {
"type": "ai_provider",
"properties": {
"name": "Formation Ollama",
"provider": "ollama",
"default_model": "qwen2.5:0.5b"
}
},
"profileMemory": {
"type": "memory",
"properties": {
"name": "Formation Profile Memory",
"tags": ["formation", "demo"]
}
},
"assistant": {
"type": "agent",
"properties": {
"name": "Formation Assistant",
"ai_provider_id": { "ref": "provider" },
"instructions": "Answer helpfully from the knowledge base.",
"knowledge_config": {
"memory_ids": [{ "ref": "profileMemory" }],
"write_memory_id": { "ref": "profileMemory" }
}
}
}
},
"outputs": {
"agent_id": { "ref": "assistant" },
"memory_id": { "ref": "profileMemory" },
"provider_id": { "ref": "provider" }
}
}
EOF
TEMPLATE=$(cat formation.json)
Step 4 — Validate the template
The validate endpoint checks structure without creating any resources. It is safe to call as many times as needed. See Agent Formation for the full validation rules.
- CLI
- SDK
- curl
soat validate-agent-formation --template "$TEMPLATE"
import { readFileSync } from 'fs';
const template = JSON.parse(readFileSync('formation.json', 'utf-8'));
const authClient = createClient(
createConfig({
baseUrl: 'http://localhost:5047',
headers: { Authorization: `Bearer ${ADMIN_TOKEN}` },
})
);
const { data: validation } = await AgentFormations.validateAgentFormation({
client: authClient,
body: { template },
});
console.log(validation.valid); // true
TEMPLATE=$(cat formation.json)
curl -s -X POST "$SOAT_URL/api/v1/agent-formations/validate" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"template\": $TEMPLATE}"
Expected output:
{ "valid": true, "errors": [] }
Step 5 — Preview the deployment plan
The plan endpoint computes what would happen if you deployed the template now — which resources would be created, updated, or deleted. No resources are touched. See Agent Formation — Planning for details.
- CLI
- SDK
- curl
soat plan-agent-formation --project_id "$PROJECT_ID" --template "$TEMPLATE"
const { data: plan } = await AgentFormations.planAgentFormation({
client: authClient,
body: { project_id: PROJECT_ID, template },
});
console.log(plan.actions);
TEMPLATE=$(cat formation.json)
curl -s -X POST "$SOAT_URL/api/v1/agent-formations/plan" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"project_id\": \"$PROJECT_ID\", \"template\": $TEMPLATE}"
The response lists each resource with an action of create, update, or none.
Step 6 — Deploy the formation
Create the formation to provision all resources in dependency order. SOAT resolves { "ref": ... } expressions after each resource is created, so the agent receives the real AI provider and memory IDs.
- CLI
- SDK
- curl
FORMATION=$(soat create-agent-formation \
--project_id "$PROJECT_ID" \
--name "my-agent-app" \
--template "$TEMPLATE")
FORMATION_ID=$(echo "$FORMATION" | jq -r '.id')
echo "FORMATION_ID: $FORMATION_ID"
echo "Outputs: $(echo "$FORMATION" | jq '.outputs')"
const { data: formation } = await AgentFormations.createAgentFormation({
client: authClient,
body: {
project_id: PROJECT_ID,
name: 'my-agent-app',
template,
},
});
const FORMATION_ID = formation.id;
console.log('Outputs:', formation.outputs);
TEMPLATE=$(cat formation.json)
FORMATION=$(curl -s -X POST "$SOAT_URL/api/v1/agent-formations" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"project_id\": \"$PROJECT_ID\", \"name\": \"my-agent-app\", \"template\": $TEMPLATE}")
FORMATION_ID=$(echo "$FORMATION" | jq -r '.id')
echo "FORMATION_ID: $FORMATION_ID"
echo "Outputs: $(echo "$FORMATION" | jq '.outputs')"
The outputs field in the response contains the physical SOAT IDs for agent_id, memory_id, and provider_id. You can use these IDs directly with the Agents API to start a conversation.
Step 7 — Inspect the deployed stack
Retrieve the formation to see its current status, managed resources, and resolved outputs.
- CLI
- SDK
- curl
soat get-agent-formation --formation_id "$FORMATION_ID"
const { data: stack } = await AgentFormations.getAgentFormation({
client: authClient,
path: { formation_id: FORMATION_ID },
});
console.log(stack.status); // "active"
console.log(stack.resources); // array of provisioned resources
curl -s "$SOAT_URL/api/v1/agent-formations/$FORMATION_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN"
The resources array shows each logical ID mapped to a physical resource ID and its status (created, updated, or deleted).
Step 8 — Update the formation
Change the agent instructions and redeploy. SOAT computes a diff and updates only the resources that changed.
- CLI
- SDK
- curl
UPDATED_TEMPLATE=$(printf '%s' "$TEMPLATE" | jq '.resources.assistant.properties.instructions = "Answer concisely from the knowledge base."')
soat update-agent-formation \
--formation_id "$FORMATION_ID" \
--template "$UPDATED_TEMPLATE"
const updatedTemplate = {
...template,
resources: {
...template.resources,
assistant: {
...template.resources.assistant,
properties: {
...template.resources.assistant.properties,
instructions: 'Answer concisely from the knowledge base.',
},
},
},
};
const { data: updated } = await AgentFormations.updateAgentFormation({
client: authClient,
path: { formation_id: FORMATION_ID },
body: { template: updatedTemplate },
});
console.log(updated.status); // "active"
UPDATED_TEMPLATE=$(cat formation.json | jq '.resources.assistant.properties.instructions = "Answer concisely from the knowledge base."')
curl -s -X PUT "$SOAT_URL/api/v1/agent-formations/$FORMATION_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"template\": $UPDATED_TEMPLATE}"
Step 9 — View operation events
Each mutating operation records events you can inspect to understand what happened, especially useful when a deployment partially fails. See Agent Formation — Operations for the event schema.
- CLI
- SDK
- curl
soat list-agent-formation-events --formation_id "$FORMATION_ID"
const { data: events } = await AgentFormations.listAgentFormationEvents({
client: authClient,
path: { formation_id: FORMATION_ID },
});
events.forEach((op) => {
console.log(op.operation_type, op.status, op.events);
});
curl -s "$SOAT_URL/api/v1/agent-formations/$FORMATION_ID/events" \
-H "Authorization: Bearer $ADMIN_TOKEN"
Step 10 — Delete the formation
Deleting a formation removes the formation record and all SOAT resources it created, in reverse dependency order.
- CLI
- SDK
- curl
soat delete-agent-formation --formation_id "$FORMATION_ID"
await AgentFormations.deleteAgentFormation({
client: authClient,
path: { formation_id: FORMATION_ID },
});
curl -s -X DELETE "$SOAT_URL/api/v1/agent-formations/$FORMATION_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN"
Confirm the formation is gone:
- CLI
- curl
# → expect-fail
soat get-agent-formation --formation_id "$FORMATION_ID"
curl -s -o /dev/null -w "%{http_code}" \
"$SOAT_URL/api/v1/agent-formations/$FORMATION_ID" \
-H "Authorization: Bearer $ADMIN_TOKEN"
# Prints 404
Summary
You deployed a multi-resource AI agent application using a single formation template. The key ideas:
- Validate before deploying to catch structural errors early.
- Plan to preview changes before they happen.
refexpressions wire resources together; SOAT resolves them in dependency order.- Outputs give you the physical IDs of deployed resources without manually tracking them.
- Update reruns the apply logic and only changes what differs.
- Delete tears down all managed resources in one call.
For the full API reference, see Agent Formation.