Skip to main content

MODELS, TYPES, AND METHODS

Why types and definitions are separate

Swamp separates model types from model definitions because they change for different reasons, are authored by different people, and live in different places.

A model type is TypeScript code. It defines methods, input and output schemas, and execution logic for interacting with an external system. A model definition is YAML configuration. It instantiates a type with specific arguments — which region, which CIDR block, which command to run. The analogy is straightforward: a type is a class; a definition is an object constructed from it.

The separation exists to make two things possible. First, types are published as extensions and shared across repositories. A collective publishes an EC2 model type once; any repo can write a definition that uses it. Second, definitions are plain structured data, not code. An agent can produce a YAML definition using general reasoning — it writes data conforming to a schema, and the type handles execution. This means an agent does not need to understand the TypeScript internals of a model type to use it. It only needs to understand the schema, which is inspectable via swamp model type describe.

The tradeoff is indirection. When something goes wrong, you need to know whether the problem is in the type (code) or the definition (configuration). Swamp surfaces this through schema validation at definition creation time — if the YAML does not match the type's input schema, the error points at the definition. If the schema validates but execution fails, the problem is in the type.

See the model definitions reference for the YAML schema and the model type reference for the TypeScript interface.

The method execution model

Methods are named operations on a model — create, start, stop, destroy, sync, lookup, and whatever else a type defines. The execution lifecycle has a deliberate shape: definition evaluation, pre-flight checks, method execution, data output write.

The ordering matters most at the pre-flight stage. Checks run before the method executes because they enforce invariants — policy constraints, dependency readiness, quota availability — that are cheaper to verify than to recover from. A half-completed cloud provisioning operation that fails mid-execution may leave orphaned resources. A check that fails before execution begins leaves nothing to clean up.

Definition evaluation resolves CEL expressions in the YAML at execution time, not at creation time. This means a definition can reference the output of another model (data.latest("vpc", "resource").attributes.id) and the value is resolved when the method runs, not when the definition is written. Late binding is what makes data chaining across models work — see How Swamp Works for the broader composition model.

After execution, the method's output is written as versioned, immutable data. This is not optional — every method execution produces data artifacts, even if the method's purpose was destructive. A destroy method writes a record that the destruction happened, what was destroyed, and when. This is what makes the data layer useful for audit and for downstream decisions.

Auto-creation vs explicit definitions

Swamp supports two paths to running a model method. The explicit path is swamp model create, which produces a definition file tracked in the repository. The implicit path is direct type execution (swamp model method run @type method_name), which auto-creates a definition in .swamp/auto-definitions/ if one does not already exist.

Auto-creation exists for two reasons: rapid prototyping and agent ergonomics. An operative exploring a new model type should not have to write a definition file before they can try a method. An agent executing a one-off task should not produce permanent configuration for something that will not be repeated.

The tradeoff is visibility. Auto-definitions are ephemeral — they are not git-tracked, do not appear in swamp model search, and are not included in workflow resolution. If a model needs to be referenced by other models, composed into a workflow, or reviewed by a teammate, it needs an explicit definition. Auto-creation is the sketch; explicit definition is the committed design.

Output specs: resources vs files

Method outputs come in two forms, and the distinction is intentional.

Resources are structured JSON conforming to a schema. They are queryable via data.latest() and composable — one model's resource output becomes another model's input through CEL expressions. Resources are the composition mechanism that makes model chaining work.

Files are opaque binary or text artifacts with MIME types — logs, generated configuration, downloaded content. They are stored and retrievable, but they are not queryable by field or composable through expressions. A file is an artifact you keep; a resource is data you build on.

The reason for the split is that treating everything as structured data would force schemas onto content that has no meaningful structure (a log file, a tarball), while treating everything as opaque files would destroy the composability that makes models useful together.

The built-in command/shell type

Every Swamp installation ships with command/shell, a model type that runs arbitrary shell commands. It exists as an escape hatch — a way to do something Swamp does not have a typed model for, without leaving Swamp's data model entirely. The command runs, and its stdout, stderr, and exit code are captured as a structured resource, queryable like any other output.

The escape hatch is deliberately uncomfortable for repeated use. Shell commands have no input schema, no output schema beyond the generic result shape, and no pre-flight checks. If you find yourself wrapping a CLI tool in command/shell across multiple definitions, that is a signal to search for a community extension (swamp extension search) or write an extension model that gives the interaction proper types. The repository convention makes this explicit: command/shell is for ad-hoc one-off commands, not for building integrations.