Skip to main content

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 | sh

On Windows, run this in PowerShell instead:

PS> irm https://swamp-club.com/install.ps1 | iex

Once it finishes, verify the installation:

$ swamp version

You 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 login

Your 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 init

You 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  workflows

Create 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-check

You 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 result

Three 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 check

You 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-summary
Created: health-summary (command/shell)

Now create the workflow:

$ swamp workflow create health-check
Created: health-check

Open 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: 1

Run the workflow

$ swamp workflow run health-check

You 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-check

Now check how many versions have accumulated for the dead endpoint:

$ swamp data versions check-dead result

You 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 result

Only 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-check as a community extension others can install.