YOUR FIRST AUTOMATION
In this tutorial, we will build a health monitor that checks three HTTP endpoints, runs the checks in parallel, summarizes the results, and keeps a queryable history of every run.
What we will build
We are going to create a typed extension called @tutorial/http-check that
measures response status, latency, and availability for any URL. We will define
three monitors from that single type — one for a real site, one for example.com,
and one that always fails. A workflow will run all three checks in parallel and
print a summary report. Every result is versioned, so we can query it later.
Install Swamp
Open your terminal and run:
$ curl -fsSL https://swamp-club.com/install.sh | shOnce it finishes, verify the installation:
$ swamp versionYou will see output like:
info version swamp "20260727.150514.0-sha...."If the command is not found, see the install guide for troubleshooting.
Initialize the repo
Create a new directory and initialize it as a Swamp repo:
$ mkdir health-monitor
$ cd health-monitor
$ swamp repo initYou will see the Swamp banner:
███████╗██╗ ██╗ █████╗ ███╗ ███╗██████╗
██╔════╝██║ ██║██╔══██╗████╗ ████║██╔══██╗
███████╗██║ █╗ ██║███████║██╔████╔██║██████╔╝
╚════██║██║███╗██║██╔══██║██║╚██╔╝██║██╔═══╝
███████║╚███╔███╔╝██║ ██║██║ ╚═╝ ██║██║
╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝
╔═══════════════════════════════════════════╗
║ WELCOME TO THE CLUB ║
║ for hackers, by hackers ║
╚═══════════════════════════════════════════╝
info repo·init Initialized swamp repository at "..." (tool: "claude")Notice the .claude/ directory and CLAUDE.md file — these are what let Claude
Code work with Swamp automatically.
$ ls -a
. .. .claude CLAUDE.md .gitignore models .swamp .swamp.yaml vaults workflowsCreate the check type
We need a typed extension that sends an HTTP request and records the result.
Note
In practice, you would ask your agent to create this extension. We include the full source here so you can see the typed result at each step.
Create the file extensions/models/http_check.ts:
$ mkdir -p extensions/models// extensions/models/http_check.ts
import { z } from "npm:zod@4";
const GlobalArgsSchema = z.object({
url: z.string().url().describe("The URL to check"),
timeout: z.number().positive().default(5000).describe(
"Request timeout in milliseconds",
),
});
const ResultSchema = z.object({
status: z.number().describe("HTTP status code, or 0 if the request failed"),
latency: z.number().describe("Response time in milliseconds"),
ok: z.boolean().describe("True when status is 2xx and the request completed"),
});
export const model = {
type: "@tutorial/http-check",
version: "2026.07.28.1",
globalArguments: GlobalArgsSchema,
resources: {
result: {
description: "Health check result for a single endpoint",
schema: ResultSchema,
lifetime: "infinite" as const,
garbageCollection: 100,
},
},
methods: {
check: {
description:
"Send an HTTP GET to the configured URL and record the result",
arguments: z.object({}),
// deno-lint-ignore no-explicit-any
async execute(_args: Record<string, never>, context: any) {
const { url, timeout } = context.globalArgs;
const start = 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 {
// Connection refused, timeout, DNS failure — status stays 0
}
const latency = Date.now() - start;
const result = { status, latency, ok };
const handle = await context.writeResource("result", "result", result);
return { dataHandles: [handle] };
},
},
},
};Verify that Swamp discovers the type:
$ swamp model type describe @tutorial/http-checkYou will see:
Type: @tutorial/http-check
Version: 2026.07.28.1
Global Arguments:
url (string) *required
timeout (number)
Data Outputs:
result [resource] - Health check result for a single endpoint (infinite)
Methods:
check - Send an HTTP GET to the configured URL and record the resultThree definitions from one type
Now create three model definitions — one per endpoint we want to monitor:
$ swamp model create @tutorial/http-check check-swamp-club \
--global-arg 'url=https://swamp-club.com' \
--global-arg 'timeout=5000'You will see:
Created: check-swamp-club (@tutorial/http-check)$ swamp model create @tutorial/http-check check-example \
--global-arg 'url=https://example.com' \
--global-arg 'timeout=5000'Created: check-example (@tutorial/http-check)For the third, use http://localhost:9 — a port that nothing listens on, so the
check will always fail:
$ swamp model create @tutorial/http-check check-dead \
--global-arg 'url=http://localhost:9' \
--global-arg 'timeout=2000'Created: check-dead (@tutorial/http-check)Notice that all three definitions share the same @tutorial/http-check type but
have different URLs and timeouts.
Run one check manually
Run the example.com check to see the typed result:
$ swamp model method run check-example checkYou will see output like:
Resolved check-example (@tutorial/http-check)
Executing check
Completed check on check-example succeeded in 187ms
┌───────────────────────────────────┐
│ Arguments │
│ url https://example.com │
│ timeout 5000 │
│ │
│ Data produced │
│ result │
│ status 200 │
│ latency 176 │
│ ok true │
│ │
└───────────────────────────────────┘Now inspect the stored data:
$ swamp data get check-example result --version 1Data: result (v1)
Model: check-example (@tutorial/http-check)
Content: application/json, 38B
Lifetime: infinite | GC: 100
Tags: type=resource, specName=result, modelName=check-example
{
"status": 200,
"latency": 176,
"ok": true
}Notice the status, latency, and ok fields matching the schema we defined.
Wire the workflow
We will create a workflow with two jobs: a check job that runs all three
monitors in parallel, and a summarize job that reads their results using CEL
expressions. First, create a shell model for the summary step:
$ swamp model create command/shell health-summaryCreated: health-summary (command/shell)Now create the workflow:
$ swamp workflow create health-checkCreated: health-checkOpen the workflow file that was created under workflows/ and replace its
contents with:
id: <keep the generated id>
name: health-check
tags: {}
jobs:
- name: check
description: Check all endpoints in parallel
steps:
- name: swamp-club
description: Check swamp-club.com
task:
type: model_method
modelIdOrName: check-swamp-club
methodName: check
dependsOn: []
weight: 0
allowFailure: false
- name: example
description: Check example.com
task:
type: model_method
modelIdOrName: check-example
methodName: check
dependsOn: []
weight: 1
allowFailure: false
- name: dead
description: Check dead endpoint
task:
type: model_method
modelIdOrName: check-dead
methodName: check
dependsOn: []
weight: 2
allowFailure: false
dependsOn: []
weight: 0
- name: summarize
description: Summarize results
steps:
- name: report
description: Print a summary of all checks
task:
type: model_method
modelIdOrName: health-summary
methodName: execute
inputs:
run: |
echo "=== Health Check Report ==="
echo ""
echo "swamp-club.com: status=${{ data.latest("check-swamp-club", "result").attributes.status }}, latency=${{ data.latest("check-swamp-club", "result").attributes.latency }}ms, ok=${{ data.latest("check-swamp-club", "result").attributes.ok }}"
echo "example.com: status=${{ data.latest("check-example", "result").attributes.status }}, latency=${{ data.latest("check-example", "result").attributes.latency }}ms, ok=${{ data.latest("check-example", "result").attributes.ok }}"
echo "localhost:9: status=${{ data.latest("check-dead", "result").attributes.status }}, latency=${{ data.latest("check-dead", "result").attributes.latency }}ms, ok=${{ data.latest("check-dead", "result").attributes.ok }}"
dependsOn: []
weight: 0
allowFailure: false
dependsOn:
- job: check
condition:
type: completed
weight: 1
version: 1Run the workflow
$ swamp workflow run health-checkYou will see output like:
system │ Starting workflow health-check
check │ start
check │ step example · check-example · check · start
check │ step swamp-club · check-swamp-club · check · start
check │ step dead · check-dead · check · start
check │ done dead in 31ms
check │ done example in 186ms
check │ done swamp-club in 3.1s
check │ completed in 3.1s
summarize │ start
summarize │ step report · health-summary · execute · start
summarize │ === Health Check Report ===
summarize │
summarize │ swamp-club.com: status=200, latency=3020ms, ok=true
summarize │ example.com: status=200, latency=160ms, ok=true
summarize │ localhost:9: status=0, latency=0ms, ok=false
summarize │ done report in 30ms
summarize │ completed in 30ms
system │ Completed workflow health-check succeeded in 3.1sNotice that the three checks in the check job all started at the same time,
and the summarize job only started after they finished. The dead endpoint
shows status=0, ok=false.
Query the history
Run the workflow again to build up history:
$ swamp workflow run health-checkNow check how many versions have accumulated for the dead endpoint:
$ swamp data versions check-dead resultYou will see multiple versions, each with a timestamp:
{
"dataName": "result",
"modelName": "check-dead",
"modelType": "@tutorial/http-check",
"versions": [
{ "version": 3, "createdAt": "2026-07-27T23:54:32.221Z", "isLatest": true },
{
"version": 2,
"createdAt": "2026-07-27T23:54:19.350Z",
"isLatest": false
},
{ "version": 1, "createdAt": "2026-07-27T23:52:58.649Z", "isLatest": false }
],
"total": 3
}Notice that each run added a new version. Now query across all models to find which checks failed:
$ swamp data query 'attributes.ok == false'┌────────┬────────────┬──────────┬──────────┬─────────┬──────┐
│ name │ modelName │ specName │ dataType │ version │ size │
├────────┼────────────┼──────────┼──────────┼─────────┼──────┤
│ result │ check-dead │ result │ resource │ 3 │ 35B │
└────────┴────────────┴──────────┴──────────┴─────────┴──────┘
1 resultOnly check-dead appears — the query matched across every model in the repo.
What we built
We built an endpoint health monitor: a typed extension, three model definitions, a workflow with parallel checks and a summary, and versioned history we can query with CEL.
Next steps
- Storing Secrets — add an API token for an authenticated endpoint using an encrypted vault.
- Publish an Extension — package
@tutorial/http-checkas a community extension others can install.