Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLI
Assigneesstack72

#302 Direct type execution: collapse model create + method run into one command

Opened by stack72 · 5/9/2026· Shipped 5/9/2026

Problem

Running a method against a model type today requires three commands:

swamp extension pull @swamp/cve/dirtyfrag
swamp model create @swamp/cve/dirtyfrag dirtyfrag-scanner
swamp model method run dirtyfrag-scanner scanFleet \
    --input hosts=34.204.74.133,3.94.251.138 \
    --input sshUser=ubuntu \
    --input sshKey=~/.ssh/dirtyfrag-fleet.pem

The model create step is pure ceremony when all values are provided at runtime via --input. Users are forced to create a persistent definition with empty or placeholder global arguments, just to have a name that model method run can resolve. For runtime-input-driven use cases — scripts, CI pipelines, one-shot CLI invocations, README examples — this friction adds no value.

The --input mechanism compounds the problem: users must mirror every runtime argument as a placeholder in the instantiated model definition, then override it with --input on every run. The definition stores nothing useful in this case.

Proposed UX

Collapse the create and run steps into a single command by providing the type inline:

swamp model @swamp/cve/dirtyfrag method run scanFleet dirtyfrag-scanner \
    --input hosts=34.204.74.133,3.94.251.138 \
    --input sshUser=ubuntu \
    --input sshKey=~/.ssh/dirtyfrag-fleet.pem

First run: The definition dirtyfrag-scanner doesn't exist yet. Swamp auto-creates it as type @swamp/cve/dirtyfrag in models/, then executes scanFleet. The definition is git-tracked, named, and works with every existing tool (model get, model output, data queries, workflows).

Subsequent runs: Finds dirtyfrag-scanner, verifies its type matches @swamp/cve/dirtyfrag (safety check), and runs. The same command is idempotent and safe to paste in scripts and READMEs.

Extension auto-resolution handles the pull step for trusted collectives, so the full experience from a clean repo is one command.

Why the definition must exist

Through design discussion, we considered whether the definition could be skipped entirely or made ephemeral. It cannot, for three reasons:

  1. Reproducibility. The definition pins the typeVersion (CalVer), which makes data output deterministic. Without a persisted definition, you lose the guarantee that "this data was produced by this exact configuration."

  2. Data provenance. Data artifacts and output records reference a definition ID. The design contract is: "data stores the definition that created it, so the model can be re-instantiated from its data." A persistent definition is what makes this work.

  3. Reusability. A named definition lets you re-run the same method later without re-specifying the type. It also makes the model visible to workflows, model get, and data queries.

The definition is the unit of reproducibility. What changes is when it's created — at explicit model create time, or implicitly on first model method run.

How it works

Auto-create-then-run

When the first positional argument to model method run starts with @, it's treated as a type reference rather than a definition name:

  1. Look up the definition by the provided name (last positional)
  2. Found: Verify definition.type matches the @type argument. Error on mismatch. Run.
  3. Not found: Auto-create a definition with that name and type in models/. Run.

The @type serves as both a creation hint (when the definition doesn't exist) and a type safety check (when it does).

Input routing

Today, --input values are split between "definition inputs" (for CEL expression evaluation) and "method argument overrides" based on the definition.inputs schema. For auto-created definitions with no stored configuration, a new splitting strategy is needed.

The type already declares both schemas:

  • globalArguments: Zod schema for values shared across all methods
  • methods[name].arguments: Zod schema for per-method values

The input router splits --input values by matching keys against both schemas:

  1. Keys matching the method's arguments schema → method arguments
  2. Keys matching the type's globalArguments schema (but not in method schema) → global arguments
  3. Keys in neither schema → rejected with a clear error listing valid keys

Method arguments take precedence when a key appears in both schemas (more specific scope wins).

Validation moves to run-time

Today, global argument validation happens at model create time via the type's globalArguments Zod schema. In the auto-create path, this validation happens at run-time instead, just before method execution. The same schema runs — it just runs at a different moment. For runtime-input-driven use cases, run-time is the only time that makes sense.

What does NOT change

  • Named definitions in models/ — completely untouched
  • model create / model edit / model delete / model get commands
  • Data persistence, output tracking, version upgrades
  • Workflow integration (workflows reference models by name, which continues working)
  • Extension auto-resolution (already works; direct execution benefits from it)
  • The model method run <name> <method> syntax (no @type = existing behavior)

Design decisions made

These were resolved during design discussion and should be treated as settled:

  • Definitions go in models/, not .swamp/. They're git-tracked, named, and work with all existing tooling. No new storage layer needed.
  • A name is always required. The definition is the unit of reproducibility. Nameless/implicit definitions were considered and rejected because they prevent reuse, output queries, and workflow references.
  • The @type argument distinguishes this path. The presence of @ in the first positional is unambiguous — existing definition names don't start with @. No new flags or subcommands needed from the user's perspective.
  • Existing model create path stays. This is complementary, not a replacement. model create is for when you want to configure a definition before running (set global args, inputs schema, driver config). Direct type execution is for when everything comes from --input.
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 11 MOREREVIEW+ 3 MOREPR_MERGEDSHIPPED

Shipped

5/9/2026, 11:39:35 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack725/9/2026, 8:47:59 PM

Sign in to post a ripple.