#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.pemThe 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.pemFirst 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:
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."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.
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:
- Look up the definition by the provided name (last positional)
- Found: Verify
definition.typematches the@typeargument. Error on mismatch. Run. - 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 methodsmethods[name].arguments: Zod schema for per-method values
The input router splits --input values by matching keys against both schemas:
- Keys matching the method's
argumentsschema → method arguments - Keys matching the type's
globalArgumentsschema (but not in method schema) → global arguments - 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 getcommands- 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
@typeargument 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 createpath stays. This is complementary, not a replacement.model createis 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.
Shipped
Click a lifecycle step above to view its details.
Sign in to post a ripple.