Introduction to Swamp
Swamp is a command-line automation framework designed to be operated by AI coding agents. It turns a task an agent worked out once into files you keep in Git: a YAML definition describing what to run, and a workflow describing what runs in which order. Every run records its output as structured, versioned data you can query later. It exists because a chat transcript is not something you can review, re-run, or hand to a colleague.
itInfrastructure and operations | OpenSkills.info
Intro
Swamp is a command-line automation framework designed to be operated by AI coding agents. It gives recurring work a durable shape. Instead of asking an agent to reason a task out from scratch in every new conversation, you keep the task as files in a repository and run those files whenever you need the result again.
This course assumes you have never used Swamp. By the end of it you will know what Swamp stores, what a single run produces, how several runs get ordered, where credentials live, and when reaching for Swamp is the wrong decision.
The problem Swamp addresses
An AI agent can work out how to check an endpoint, collect an inventory of cloud resources, or verify a security policy. It solves the task once, inside a conversation. That conversation is not something a colleague can review, not something you can run again next month, and not something you can compare against last month's answer.
Swamp splits that work into two parts that behave differently and live in different places:
- The instructions — which kind of work to do, against which target, with which configuration, and in which order. These are YAML files you commit to Git and review like any other code.
- The results — what each run actually produced. These go into a local data store where every write gets a name and a version number, so a later run can read an earlier run's output instead of recomputing it.
The rest of this course is detail on those two parts.
A model has a type and a definition
A model is the unit of work in Swamp. Every model has two halves, and keeping them straight is the single most useful thing to learn first.
A model type is reusable TypeScript code. It declares the methods you can call, the schema of the arguments each method accepts, and the shape of the data each method writes. The type contains all the behavior: how to authenticate, which API to call, how to interpret a response. The official documentation compares this to a class in object-oriented programming.
A model definition is a YAML file that configures one particular use of a type. It fills in the specifics: which URL, which account, which region, which timeout. Continuing the comparison, a definition is an object constructed from the class.
Consider an endpoint health check. You write the type once. It knows how to make an HTTP request, measure how long it took, and record whether the response was healthy. You then create three definitions from that one type:
@course/http-check (type — TypeScript, written once)
├── check-a (definition — url: https://example.com)
├── check-b (definition — url: https://swamp-club.com)
└── check-c (definition — url: http://localhost:9, timeoutMs: 2000)
Three targets, three definitions, one piece of code. When you fix a bug in how latency is measured, you fix it in the type and all three definitions inherit the fix.
This split exists for a specific reason: definitions are plain data. An agent can write a correct definition by reading the type's published schema, without understanding TypeScript or the API behind it. That is why Swamp asks you to write types rarely and definitions often.
What a method run produces
A method is a named operation on a model — check, create, start, sync, lookup, or whatever the type defines. You invoke one with swamp model method run <definition-name> <method-name>.
Every execution produces data outputs, even when the method's purpose was to destroy something. Swamp records the outcome so that an audit trail exists and so that later steps have something to read. There are two kinds of output:
- A resource is structured JSON that conforms to the schema the type declared. Resources are queryable, so you can search across every resource in the repository with a filter expression.
- A file is an opaque binary or text artifact with a MIME type. Files are stored and retrievable, but Swamp does not look inside them, so you cannot query their contents.
The built-in command/shell type illustrates both: it writes a result resource holding the exit code and a log file holding the raw output.
Each write to the same output name creates a new version, numbered from 1. Version 1 is not overwritten when version 2 arrives, so swamp data versions shows you how an answer changed over time. How long versions survive depends on two settings the type declares: a lifetime, which decides when data expires, and a garbage collection policy, which caps how many versions are kept. The five lifetimes are ephemeral (held in memory only and discarded when the process ends), job, workflow, a duration string such as 30d, and infinite. None of this is an unlimited archive unless the type asked for infinite with a generous version cap.
What gets committed and what stays on your machine
This distinction trips up almost everyone, because Swamp uses the word "versioned" for something Git is not doing.
Definitions and workflows are ordinary repository files in models/ and workflows/. You commit them, review them in a pull request, and a teammate who clones the repository gets them.
Execution data is different. It lives in .swamp/, and swamp repo init writes a managed section into your .gitignore that excludes that directory, labelled "Runtime data (not needed in version control)". So the data is versioned by Swamp, on the machine that ran the method — it is not committed, not pushed, and not present for a teammate who clones the repository. Their swamp data get returns nothing until they run the method themselves.
Read the boundary this way:
models/, workflows/, .swamp.yaml -> Git: reviewed, shared, portable
.swamp/ -> local runtime data: versioned by Swamp,
gitignored, machine-specific
Two practical consequences follow. First, treat local run history as evidence you can inspect and re-create, not as a permanent record of record — a laptop reinstall takes it with it. Second, when a team needs shared history, that comes from pointing Swamp at a shared datastore backend, not from committing .swamp/ to the repository.
Workflows put methods in order
A workflow is a directed acyclic graph of jobs, where each job contains steps. A job is the unit of dependency; a step is a single unit of work.
The default is parallel. Jobs with no dependency on each other execute at the same time, and so do steps within a job. You do not switch parallelism on — you switch it off, by declaring a dependency with dependsOn.
A dependency states two things: what must finish first, and which terminal state permits the dependent work to start. That second part is the condition, and choosing it deliberately is what makes a workflow behave the way you intended:
| Condition | The dependent job or step runs when the dependency… |
|---|---|
succeeded | finished successfully |
failed | finished with a failure |
completed | finished either way |
skipped | was skipped |
always | reached any state at all |
The pairing that matters most is succeeded versus completed. Gate a deployment on succeeded, because it must not proceed when its checks failed. Gate a report or a cleanup task on completed, because those are needed precisely when something went wrong. A report gated on succeeded disappears on exactly the run you wanted to read.
A step's task declares what it does. There are four task types: model_method runs a method on a definition, workflow calls another workflow, manual_approval suspends the run until a person approves or rejects it, and assert evaluates an expression and records a pass or fail with a message and a severity.
CEL expressions carry values between steps
Steps pass data to each other with CEL, the Common Expression Language. A later step reads an earlier step's output by name rather than through a temporary file both sides have to agree on:
data.latest("check-a", "result").attributes.ok
That reads the most recent version of the result output written by the check-a definition and takes its ok field. Related forms include data.version("check-a", "result", 2) for a specific version and data.query(...) for a filter across many outputs. Swamp calls this pattern data chaining.
One behavior surprises people. Inside a workflow, expressions are wrapped in ${{ }}. When the wrapper is the entire value, the result keeps its native type — a number stays a number, a boolean stays a boolean. As soon as you surround it with other text, the result becomes a string. "${{ data.latest('m','d').attributes.code }}" gives you a number; "status=${{ ... }}" gives you the string status=200. That is harmless when feeding a shell command and breaks a downstream numeric comparison.
Vaults keep credentials out of your files
A vault is a named secret store. Definitions and workflows refer to a secret by name, with an expression such as vault.get("prod-secrets", "api-token"), and Swamp resolves the actual value when the step executes — not when you write the definition.
The reason is the boundary described earlier. A definition is a Git-committed file, so a token pasted into its arguments is a token in your repository history. A vault reference keeps the definition safe to commit and safe to share, and Swamp redacts values that a type has marked sensitive when it writes output.
Choose the smallest level of automation that fits
Swamp's own guidance is to search before you build, in this order:
- Search the extension registry. An extension is a packaged, versioned bundle that can ship model types, workflows, vault providers, and more. Someone may already model the service you need.
- Search the types already installed in your repository, and inspect a promising one with
swamp model type describe. - Extend an existing type when it covers the right domain but lacks the one operation you want. Adding a method is cheaper than duplicating an integration.
- Write a new type only when no available integration represents the API or system you need.
The command/shell type deserves a specific warning. It exists for genuinely one-off shell work. It is not a way to wrap a CLI tool or an HTTP API, because doing so throws away everything the type system gives you: a schema, validated arguments, and a queryable output shape. If you find yourself running the same shell command from several definitions, that is the signal to write a type.
Where Swamp does not belong
Swamp fits repeated checks, inventory collection, policy verification, reporting, and multi-step operations where you want the configuration reviewed and the results kept.
It is not an infrastructure-as-code tool. Terraform, OpenTofu, and Ansible own the desired state of infrastructure and configuration, and Swamp does not replace them — it orchestrates operational tasks around them.
It is also unnecessary for a task that will happen once. Writing a type and a definition for a question you will never ask again costs more than answering the question. Reach for Swamp on the second or third repetition, when the cost of re-deriving the work exceeds the cost of writing it down.
Where this skill leads
Relevant careers
See how this topic contributes to broader role-level skill maps.
Sources
- https://swamp-club.com/manual
Supports
- Manual navigation and official documentation structure
- https://swamp-club.com/manual/tutorials/your-first-automation
Supports
- Typed HTTP-check model example
- Parallel health-check workflow and queryable version history
- Repository initialization and definition creation
- https://swamp-club.com/manual/reference/model-definitions
Supports
- Difference between a model type and model definition
- Model-definition storage and method execution commands
- Named output specifications and model validation
- https://swamp-club.com/manual/reference/workflows
Supports
- Workflow jobs, steps, dependencies, and conditions
- Step task types model_method, workflow, manual_approval, and assert, including the required expr and message fields and the optional severity that defaults to high
- Parallel execution and completed versus succeeded conditions
- Workflow validation and execution commands
- https://git.swamp-club.com/swamp-club/swamp/src/branch/main/integration/workflow_assert_test.ts
Supports
- Upstream implementation and version floor of the assert step type
- https://swamp-club.com/manual/reference/data
Supports
- Resource and file outputs
- Versioning, data lifetimes, garbage collection, and CEL queries
- Data get, versions, and query commands
- https://swamp-club.com/manual/reference/cel-expressions
Supports
- CEL wrappers, native-type preservation, and string coercion
- data.latest, data.version, data.query, and null-safe access
- https://swamp-club.com/manual/reference/vaults
Supports
- Vault-backed secret resolution and sensitive output handling
- Vault creation and secret-management commands
- https://swamp-club.com/manual/how-to/install-swamp
Supports
- Installation and PATH verification
- https://swamp-club.com/extensions
Supports
- Extension discovery before authoring a custom integration
- https://git.swamp-club.com/swamp-club/swamp/src/branch/main
Supports
- Current official Swamp source repository
- Contribution and license material
- https://swamp-club.com/pricing
Supports
- Free single-person use and compensated-team billing conditions
- Agents and CI excluded from seat counts
