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 | shOn Windows, run this in PowerShell instead:
PS> irm https://swamp-club.com/install.ps1 | iexOnce 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.
Sign in
Every Swamp command beyond swamp version needs a swamp-club.com account, so we
sign in next:
$ swamp auth loginYour browser opens at swamp-club.com. Once you sign in, the terminal shows Authenticated with your username. If you are already signed in, skip this step.
Initialize the repo
We set the repo up by hand so we can build each piece ourselves. To have an AI
agent build around your own task instead, use
swamp start.
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 ║
╚═══════════════════════════════════════════╝
Initialized swamp repository at ... (tools: claude)
Created vault default (local_encryption) for secrets and sensitive outputs
Your Swamp repo is ready.
...Notice the .claude/ directory and CLAUDE.md file — these are what let Claude
Code work with Swamp automatically.
$ ls -a
. .. .claude CLAUDE.md .gitignore grants 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/behaviors/http_check.ts:
$ mkdir -p extensions/behaviors// extensions/behaviors/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 behavior = {
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 behavior 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 behavior definitions — one per endpoint we want to monitor:
$ swamp behavior 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 behavior 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 behavior 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 behavior run check-example checkYou will see output like:
Resolved check-example (@tutorial/http-check)
Executing check · 04:02:09 UTC
Completed check on check-example succeeded in 181ms · 04:02:09 UTC
┌───────────────────────────────────┐
│ Arguments │
│ url https://example.com │
│ timeout 5000 │
│ │
│ Data produced │
│ result │
│ status 200 │
│ latency 174 │
│ ok true │
│ │
└───────────────────────────────────┘Now inspect the stored data:
$ swamp data query 'behaviorName == "check-example" && name == "result" && version == 1' \
--single --json --select content{
"status": 200,
"latency": 174,
"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 behavior for the summary step:
$ swamp behavior 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: behavior
behavior: check-swamp-club
methodName: check
dependsOn: []
weight: 0
allowFailure: false
- name: example
description: Check example.com
task:
type: behavior
behavior: check-example
methodName: check
dependsOn: []
weight: 1
allowFailure: false
- name: dead
description: Check dead endpoint
task:
type: behavior
behavior: 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: behavior
behavior: 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 · 04:02:27 UTC
check │ start 04:02:27 UTC
check │ step dead · check-dead · check · start 04:02:27 UTC
check │ step example · check-example · check · start 04:02:27 UTC
check │ step swamp-club · check-swamp-club · check · start 04:02:27 UTC
check │ done dead in 10ms · 04:02:27 UTC
check │ done example in 370ms · 04:02:27 UTC
check │ done swamp-club in 933ms · 04:02:28 UTC
check │ completed in 944ms · 04:02:28 UTC
summarize │ start 04:02:28 UTC
summarize │ step report · health-summary · execute · start 04:02:28 UTC
summarize │ === Health Check Report ===
summarize │
summarize │ swamp-club.com: status=200, latency=910ms, ok=true
summarize │ example.com: status=200, latency=355ms, ok=true
summarize │ localhost:9: status=0, latency=0ms, ok=false
summarize │ done report in 44ms · 04:02:28 UTC
summarize │ completed in 47ms · 04:02:28 UTC
system │ Completed workflow health-check succeeded in 1.0s · 04:02:28 UTC
┌────────────────────────────┐
│ Data produced │
│ result from check │
│ result from check │
│ result from check │
│ result from summarize │
│ │
└────────────────────────────┘Notice 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",
"modelId": "6d2fa993-ff5b-4c1a-bc66-ffcd43ecea6d",
"modelName": "check-dead",
"modelType": "@tutorial/http-check",
"versions": [
{
"version": 2,
"createdAt": "2026-10-14T04:02:32.609Z",
"size": 35,
"checksum": "18d0eb89...",
"isLatest": true
},
{
"version": 1,
"createdAt": "2026-10-14T04:02:27.627Z",
"size": 35,
"checksum": "a114da2a...",
"isLatest": false
}
],
"total": 2
}Notice that each run added a new version. Now query across all behaviors to find which checks failed:
$ swamp data query 'attributes.ok == false'┌────────┬────────────┬──────────┬──────────┬─────────┬──────┐
│ name │ modelName │ specName │ dataType │ version │ size │
├────────┼────────────┼──────────┼──────────┼─────────┼──────┤
│ result │ check-dead │ result │ resource │ 2 │ 35B │
└────────┴────────────┴──────────┴──────────┴─────────┴──────┘
1 resultOnly check-dead appears — the query matched across every behavior in the repo.
What we built
We built an endpoint health monitor: a typed extension, three behavior 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.