Swamp tutorial: make AI agent work repeatable
By Flavio Copes
Learn how Swamp turns one-off AI agent work into typed, repeatable automation with models, workflows, assertions, and versioned data.
An AI agent can solve a difficult task once, then solve it differently tomorrow.
The second solution may still work. Or it may skip a check, use another tool, or forget an important detail that only existed in the previous conversation.
Swamp is built for this problem.
You let the agent figure out the task. Swamp turns the result into typed models and workflows that run the same way next time.
The example that made it click for me was a personal voice recorder. A spoken note could become a reminder, a research task, or a coding job. An agent decided what the note meant, then Swamp ran the right automation and updated the data.
That split is the important part:
Agent decides what to build
↓
Swamp runs it repeatably
↓
Versioned data records what happened
Let’s see how Swamp works, then build a small automation with it.
What Swamp is
Swamp is an open source automation runtime designed for AI agents.
It is not another chat interface. It is also not an agent model.
It runs under the agent as a command-line tool. Codex, Claude Code, Cursor, or another coding agent can use it to create and run automation.
Swamp gives the agent a small set of durable building blocks:
- Models describe one typed operation
- Workflows connect operations into a dependency graph
- Data stores versioned outputs from every run
- Vaults keep credentials out of files and logs
- Extensions package reusable models and integrations
- Skills teach the coding agent how to use all of this
If you know how MCP works, the difference is useful.
MCP exposes tools to an agent. Swamp gives the agent an execution layer where it can turn successful work into a repeatable system.
You can use both. They solve different problems.
The idea behind models
A Swamp model has two parts.
The model type contains the reusable logic. It defines the accepted inputs, available methods, and produced data.
The model definition configures one use of that type.
Suppose we create an HTTP check type. The type knows how to request a URL and measure its response.
We can then create several definitions from it:
HTTP check type
├── check-primary → https://example.com
└── check-backup → https://example.org
The behavior stays in one place. Each definition only supplies a URL and timeout.
This is more useful than asking an agent to invent two separate checks. The model type becomes a reusable capability.
Install Swamp
Swamp supports macOS and Linux.
Install the CLI using the official script:
curl -fsSL https://swamp-club.com/install.sh | sh
If you do not run remote scripts directly, open the script in your browser and inspect it first.
Restart your shell, then verify the installation:
swamp version
The binary is installed in ~/.local/bin. Add that directory to your PATH if the command is not found.
Create a Swamp repository for Codex
Create a new Git repository for the tutorial:
mkdir swamp-endpoint-check
cd swamp-endpoint-check
git init
Initialize Swamp and tell it we use Codex:
swamp repo init --tool codex
Swamp creates a .swamp.yaml file, plus directories for models, workflows, vaults, and runtime data.
It also adds Swamp instructions to AGENTS.md and installs its skills under .agents/skills/.
Open Codex in this directory. You can now ask it to get started with Swamp:
$swamp-getting-started
The skill gives Codex the current commands and file formats. This matters because Swamp expects the agent to operate the framework for you.
We will still create the pieces by hand in this tutorial. I want you to see what the agent will build.
Create a typed HTTP check
Create extensions/models/http_check.ts.
This file defines one model type with a check method:
import { z } from 'npm:zod@4'
const GlobalArguments = z.object({
url: z.string().url(),
timeout: z.number().positive().default(5000),
})
const Result = z.object({
status: z.number(),
latency: z.number(),
ok: z.boolean(),
})
export const model = {
type: '@local/http-check',
version: '2026.08.21.1',
globalArguments: GlobalArguments,
resources: {
result: {
description: 'HTTP endpoint check result',
schema: Result,
lifetime: 'infinite' as const,
garbageCollection: 100,
},
},
methods: {
check: {
description: 'Request the endpoint and record its status',
arguments: z.object({}),
async execute(
_args: Record<string, never>,
context: any,
) {
const { url, timeout } = context.globalArgs
const startedAt = Date.now()
let status = 0
let ok = false
try {
const response = await fetch(url, {
signal: AbortSignal.timeout(timeout),
})
status = response.status
ok = response.ok
} catch {
// A network error stays status 0 and ok false
}
const result = {
status,
latency: Date.now() - startedAt,
ok,
}
const handle = await context.writeResource(
'result',
'result',
result,
)
return { dataHandles: [handle] }
},
},
},
}
The Zod schemas are not only documentation. Swamp uses them to validate arguments and saved results.
The result resource has an infinite lifetime and keeps its latest 100 versions. We will query those versions later.
Check that Swamp can find the type:
swamp model type describe @local/http-check
You should see the url and timeout arguments, the result resource, and the check method.
Create two model definitions
Now create two endpoint checks from the same type:
swamp model create @local/http-check check-primary \
--global-arg 'url=https://example.com' \
--global-arg 'timeout=5000'
Create the second definition:
swamp model create @local/http-check check-backup \
--global-arg 'url=https://example.org' \
--global-arg 'timeout=5000'
Both definitions use the same code. Their configuration is stored as YAML under models/, where you can review and commit it.
Run one check directly:
swamp model method run check-primary check
Swamp prints the arguments and the typed data produced by the run.
Inspect the stored result:
swamp data get check-primary result
The output looks like this:
{
"status": 200,
"latency": 184,
"ok": true
}
The latency will be different on your machine.
Connect the checks in a workflow
A workflow is a directed acyclic graph, or DAG.
Steps without dependencies run in parallel. Later jobs can wait for earlier jobs to complete.
Create an empty workflow:
swamp workflow create endpoint-check
Swamp creates a YAML file under workflows/. Open it, keep its generated id, and replace the rest with this:
id: <keep the generated id>
name: endpoint-check
tags:
purpose: tutorial
jobs:
- name: collect
description: Check both endpoints in parallel
steps:
- name: primary
task:
type: model_method
modelIdOrName: check-primary
methodName: check
- name: backup
task:
type: model_method
modelIdOrName: check-backup
methodName: check
- name: verify
description: Verify the collected results
dependsOn:
- job: collect
condition:
type: completed
steps:
- name: primary-is-healthy
task:
type: assert
expr: data.latest("check-primary", "result").attributes.ok == true
message: The primary endpoint must be healthy
severity: high
- name: backup-is-healthy
task:
type: assert
expr: data.latest("check-backup", "result").attributes.ok == true
message: The backup endpoint must be healthy
severity: high
version: 1
The two steps in collect have no dependency between them. Swamp can run them at the same time.
The verify job waits for collect. Its assertions read the latest typed results using CEL expressions.
Validate the workflow before running it:
swamp workflow validate endpoint-check
You can also inspect its graph:
swamp workflow get endpoint-check --graph
Now run it:
swamp workflow run endpoint-check
The workflow succeeds when both endpoints return a successful HTTP status.
Make the workflow fail on purpose
Open the model definition containing name: check-backup under models/.
Change its URL to a local port that should not be listening:
globalArguments:
url: http://localhost:9
timeout: 2000
Run the workflow again:
swamp workflow run endpoint-check
The HTTP model still produces a valid result:
{
"status": 0,
"latency": 2,
"ok": false
}
Then the assertion fails the workflow.
This separation is useful. The model records what happened. The workflow decides whether that state is acceptable.
Query the run history
Every check produced another version of result.
List the versions for the backup check:
swamp data versions check-backup result
Now search all stored data for failed checks:
swamp data query 'attributes.ok == false'
You do not need to parse logs or ask an agent what happened. The result is structured data with a schema, version, timestamp, and model identity.
This is one of the strongest ideas in Swamp.
The output of automation becomes input for later automation.
Let the agent extend the system
Now that the repository contains a working pattern, you can ask Codex to change it.
For example:
Add a third endpoint to the endpoint-check workflow.
Run all HTTP checks in parallel.
Add an assertion that fails when latency is over 1000ms.
Validate the models and workflow, then show me the graph.
Codex can read the installed Swamp skill, inspect the existing model type, create the new definition, update the workflow, and run validation.
The agent still reasons freely while building the change.
Once the YAML and TypeScript are saved, the next run follows those definitions. It does not need to rediscover the process from chat history.
Where vaults fit
Our endpoints are public, so the tutorial does not need credentials.
Real automation usually does.
Do not put API keys inside model definitions or workflow YAML. Swamp vaults keep secrets on a separate path and expose them through expressions such as:
vault.get('production', 'api-token')
Swamp can redact secret values from output and keep credential access separate from the versioned data layer.
This is important when an agent builds the automation. The agent needs permission to use a credential without copying it into generated files or logs.
How I would use Swamp
I would start with release verification for the software I publish through Prototyped.
Each project needs a slightly different release process, but the shape repeats:
- Run tests.
- Build the release artifact.
- Check the source package for private files and secrets.
- Verify the documentation and checksums.
- Run a smoke test.
- Record the result.
An agent is useful when creating that workflow for a new project. It can inspect the repository and work out which commands and checks apply.
After that, I want the release check to be boring.
I do not want the agent to invent a new verification process every time I publish an update. I want the same typed steps, the same gates, and a clear history of every result.
I would not move every existing script into Swamp.
If a small shell script already does one stable job well, I would keep it. Swamp becomes interesting when the task crosses several systems, the agent is discovering integrations, the result must be inspected later, or several people need to share the automation.
What to watch out for
Swamp is a large system for a specific problem.
It makes sense when you need repeatability, typed outputs, secrets, history, parallel work, or reusable integrations.
It is too much for a one-line cron job.
Also remember that an extension is executable code. Review third-party extensions before trusting them, just as you would review a package or an agent tool.
Swamp is licensed under AGPL-3.0 with an additional extension and definition exception. Check the current license and pricing before using it inside a commercial team or service.
Finally, deterministic execution does not make a bad workflow correct.
Swamp will repeat the model and workflow you give it. Tests, assertions, narrow credentials, and human approval still matter for dangerous actions.
I wrote more about that boundary in how to let an AI agent perform irreversible actions safely.
The useful mental model
The simplest way to understand Swamp is this:
Use the agent to build the machine. Use Swamp to run the machine.
The agent remains useful where judgment and adaptation matter.
Swamp takes over where repetition, validation, and history matter.
That is a good boundary for AI automation.