MODEL
A model is a TypeScript module that automates resource lifecycle operations.
Models declare arguments, output specifications, and methods. Each method
receives a context object with access to data repositories, vault secrets, and
output writers. The file lives in extensions/models/ and exports a model
object.
For the YAML definition layer that configures model instances, see Model Definitions.
Export Shape
import { z } from "npm:zod@4";
export const model = {
type: "@mycollective/my-model",
version: "2026.05.14.1",
globalArguments: z.object({
region: z.string().describe("Target region"),
}),
resources: {
instance: {
description: "Managed instance",
schema: z.object({
id: z.string(),
status: z.string(),
}),
lifetime: "infinite",
garbageCollection: 3,
},
},
methods: {
create: {
description: "Create a new instance",
arguments: z.object({
name: z.string(),
}),
async execute(args, context) {
// implementation
},
},
},
};Top-Level Fields
| Field | Type | Required | Description |
|---|---|---|---|
type |
string |
Yes | Unique identifier in @collective/name format. |
version |
string |
Yes | CalVer format: YYYY.MM.DD.MICRO. |
globalArguments |
z.ZodTypeAny |
No | Zod schema for arguments shared across all methods. |
resources |
Record<string, ResourceOutputSpec> |
No | Named resource output specifications. |
files |
Record<string, FileOutputSpec> |
No | Named file output specifications. |
methods |
Record<string, MethodDefinition> |
Yes | Named methods the model exposes. |
checks |
Record<string, CheckDefinition> |
No | Pre-flight checks that run before methods. |
reports |
string[] |
No | Report names to run by default after method execution. |
upgrades |
VersionUpgrade[] |
No | Version migration chain for schema changes. |
MethodDefinition
Each method defines its arguments and an execute function.
| Field | Type | Required | Description |
|---|---|---|---|
description |
string |
Yes | What the method does. |
kind |
"create" | "read" | "update" | "delete" | "list" | "action" |
No | Semantic intent of the method. |
arguments |
z.ZodTypeAny |
Yes | Zod schema for method-specific arguments. |
execute |
(args, context) => Promise<MethodResult> |
Yes | Implementation function. |
MethodContext
The context parameter passed to every execute call. The most commonly used
fields are listed below.
Identity
| Field | Type | Description |
|---|---|---|
modelType |
string |
The model's type identifier. |
modelId |
string |
Instance ID of the model definition. |
methodName |
string |
Name of the currently executing method. |
globalArgs |
Record<string, unknown> |
Resolved global arguments. |
repoDir |
string |
Absolute path to the repository root. |
signal |
AbortSignal |
Cancellation signal. |
Definition Metadata
| Field | Type | Description |
|---|---|---|
definition |
{ id, name, version, tags } |
Model definition metadata. |
Data Operations
| Field | Signature | Description |
|---|---|---|
writeResource |
(specName, name, data, overrides?) => Promise<DataHandle> |
Write a resource — validates against schema, returns a handle. |
readResource |
(instanceName, version?) => Promise<Record<string, unknown> | null> |
Read a previously stored resource by instance name. |
readModelData |
(modelName, specName?) => Promise<DataRecord[]> |
Read the latest version of another model's data by name. |
queryData |
(predicate, select?) => Promise<DataRecord[] | unknown[]> |
Query data artifacts across all models using a CEL predicate. |
createFileWriter |
(specName, name, overrides?) => DataWriter |
Create a file writer for binary or streaming content. |
readModelData returns the latest version of each matching data item, or an
empty array when the model has no data or does not exist. Each DataRecord
carries the parsed JSON in attributes, plus name, version, specName,
modelId, modelType and tags.
const records = await context.readModelData!("my-vpc", "state");
const vpc = records[0]?.attributes as { VpcId: string } | undefined;queryData uses the same predicate language as swamp data query and returns
latest versions only, unless the predicate references version or isLatest.
Without select it returns DataRecord[]; with select it returns the
projected values.
const subnets = await context.queryData!(
'modelType == "@myorg/subnet" && attributes.vpcId == "vpc-123"',
);Model Invocation
| Field | Signature | Description |
|---|---|---|
runModel |
(options: RunModelOptions) => Promise<RunModelResult> |
Invoke another model's method from within the current execution. |
RunModelOptions takes one of two forms:
// An existing definition, by name
{ definition: string; method: string; arguments?: Record<string, unknown> }
// Direct type execution — auto-creates the definition if it does not exist
{
modelType: string;
name: string;
method: string;
arguments?: Record<string, unknown>;
}In the direct type form, name is the definition name. When no definition with
that name exists, one is auto-created in .swamp/auto-definitions/. When one
exists with a different model type, the invocation fails.
arguments is split between the target's global arguments and method arguments
by their schemas. Method arguments win on ambiguous keys; unknown keys fail the
invocation.
RunModelResult is a discriminated union on ok:
// Success
{ ok: true, resources: DataHandle[] }
// Failure
{ ok: false, error: { message: string; stack?: string } }runModel reports failure in the returned value and does not throw. Check ok
on every result:
const result = await context.runModel!({
definition: "my-vpc",
method: "read",
});
const created = await context.runModel!({
modelType: "@myorg/vpc",
name: "my-new-vpc",
method: "create",
arguments: { cidrBlock: "10.0.0.0/16" },
});
for (const r of [result, created]) {
if (!r.ok) throw new Error(r.error.message);
}The caller's extension must declare the target model's extension in its manifest
dependencies — see the
manifest reference.
Invocations from extensions not listed in dependencies are rejected at runtime.
Same-extension calls, built-in model types and user-authored model types need no
declaration.
Safety limits apply to all runModel calls within a single top-level execution:
maximum call depth of 10, cycle detection (a model cannot invoke itself directly
or transitively), and a ceiling of 100 total invocations. Vault secrets are
isolated per extension — the invoked model cannot access the caller's vault
bindings.
Data written by the invoked model is owned by the target model's definition, not
the caller's. The returned DataHandle array references the target's data.
Availability by Context
| API | Local method | Remote worker | Checks |
|---|---|---|---|
readModelData |
Yes | Yes | No |
queryData |
Yes | Yes — select is rejected; errors if it matches access-control or infrastructure model data |
No |
runModel |
Yes | No — returns { ok: false } |
No |
On a remote worker, runModel returns a failure result whose error.message
reads:
context.runModel() is not available in remote execution — move the runModel call to a local orchestrator model or workflow.To orchestrate models on workers, chain model_method steps in a workflow. See
Remote Execution for the capability
proxying model.
Extension Files
| Field | Signature | Description |
|---|---|---|
extensionFile |
(relPath: string) => string |
Resolve a path relative to the extension root. |
Services
| Field | Type | Description |
|---|---|---|
logger |
Logger |
Structured logger. |
dataRepository |
UnifiedDataRepository |
Low-level data access. |
vaultService |
VaultService (optional) |
Access to vault secrets. |
dataRepository Methods
The dataRepository field on MethodContext provides low-level data access.
Most methods should use writeResource, readResource, and queryData instead
— dataRepository is for operations those helpers don't cover.
Author-facing methods:
| Method | Returns | Description |
|---|---|---|
getContent(type, modelId, dataName, ver?) |
Uint8Array | null |
Get raw content bytes. |
findByName(type, modelId, dataName, ver?) |
Data | null |
Get data metadata (tags, version, etc). |
findAllForModel(type, modelId) |
Data[] |
List all data for a model instance. |
delete(type, modelId, dataName, ver?) |
void |
Delete a data instance — all versions, or just one. |
Pass context.modelType and context.modelId for the first two parameters when
operating on the current model's own data.
await context.dataRepository.delete(
context.modelType,
context.modelId,
"error-target-a",
);Runtime-internal methods (save, append, rename, removeLatestMarker,
collectGarbage) are used by the framework to manage data lifecycle. They are
not intended for direct use in model methods.
MethodResult
Returned by execute to report what the method produced.
| Field | Type | Required | Description |
|---|---|---|---|
dataHandles |
DataHandle[] |
No | Handles to resources or files written. |
followUpActions |
FollowUpAction[] |
No | Actions to execute after the method returns. |
DataHandle
| Field | Type | Description |
|---|---|---|
name |
string |
Instance name of the output. |
specName |
string |
Name of the resource or file spec. |
kind |
"resource" | "file" |
Whether this is structured data or a file. |
version |
number |
Version number of this output. |
size |
number |
Size in bytes. |
tags |
Record<string, string> |
Output tags. |
ResourceOutputSpec
Declares a named resource output that methods can write to.
| Field | Type | Required | Description |
|---|---|---|---|
description |
string |
No | What this resource represents. |
schema |
z.ZodTypeAny |
Yes | Zod schema that validates written data. |
lifetime |
"infinite" | "session" |
Yes | How long the data persists. |
garbageCollection |
number |
Yes | Number of versions to keep. |
tags |
Record<string, string> |
No | Default tags for this output. |
sensitiveOutput |
boolean |
No | Auto-vault sensitive fields on write. |
vaultName |
string |
No | Vault name for sensitive field storage. |
FileOutputSpec
Declares a named file output.
| Field | Type | Required | Description |
|---|---|---|---|
description |
string |
No | What this file represents. |
contentType |
string |
Yes | MIME type (e.g., application/gzip). |
lifetime |
"infinite" | "session" |
Yes | How long the data persists. |
garbageCollection |
number |
Yes | Number of versions to keep. |
streaming |
boolean |
No | Whether the file supports streaming writes. |
tags |
Record<string, string> |
No | Default tags for this output. |
Checks
Pre-flight checks run before method execution and can prevent it.
checks: {
credentials: {
description: "Verify API credentials are valid",
labels: ["auth"],
async execute(context) {
// validate credentials
return { pass: true };
},
},
},| Field | Type | Required | Description |
|---|---|---|---|
description |
string |
Yes | What the check verifies. |
labels |
string[] |
No | Tags for filtering checks. |
appliesTo |
string[] |
No | Method names this check applies to. |
execute |
(context) => Promise<{ pass, errors? }> |
Yes | Run the check. Return pass: false with errors to block execution. |
Version Upgrades
Declare a migration chain for schema changes between versions.
upgrades: [
{
toVersion: "2026.05.14.2",
description: "Rename 'instanceId' to 'id'",
upgradeAttributes(old) {
return { ...old, id: old.instanceId };
},
},
],| Field | Type | Description |
|---|---|---|
toVersion |
string |
Target version (CalVer). |
description |
string |
What the upgrade does. |
upgradeAttributes |
(old: Record<string, unknown>) => Record<string, unknown> |
Transform stored attributes to the new schema. |
Entries are ordered by toVersion, and the last entry's toVersion must equal
the model's version. For a version bump with no schema change, add an entry
with upgradeAttributes: (old) => old; without one, existing instances are
never advanced to the new version.
Upgrades run before a method executes, against each instance's recorded
typeVersion:
Instance typeVersion |
Result |
|---|---|
| Valid CalVer, behind | Every entry with a toVersion later than it runs in order. The instance is saved with the new globalArguments and a typeVersion equal to the last entry. |
| Valid CalVer, current | No entry runs. |
| Absent | No entry runs, and typeVersion is not added. The method runs with globalArguments as written, and a warning is logged. |
| Not valid CalVer | The method run fails. |
An instance whose typeVersion is behind with no entry covering the gap is
reported by swamp model get as stranded, and nothing migrates it. See
Staleness.
Packaging
Declare the model in your extension's manifest:
models:
- my_model.tsThe file path is relative to the extensions/models/ directory within the
extension root.
Runtime Permissions
Extension model methods run in-process with individually scoped Deno permissions. The following flags are granted:
| Flag | Scope |
|---|---|
--allow-read |
Filesystem read access. |
--allow-write |
Filesystem write access. |
--allow-env |
Environment variable access. |
--allow-run |
Subprocess execution. |
--allow-sys |
System information access. |
--allow-net |
Network access. |
--allow-all and --allow-ffi are not granted.
Device Node Limitation
Deno.open() on character or block device nodes (e.g. /dev/ttyUSB0) does not
work in the compiled binary. This is a Deno compiled-binary behavior, not a
Swamp restriction.
Subprocess Workaround
Deno.Command spawns subprocesses for device I/O under the granted
--allow-run permission.
const command = new Deno.Command("cat", {
args: ["/dev/ttyUSB0"],
stdout: "piped",
});
const { stdout } = await command.output();
const data = new TextDecoder().decode(stdout);const stty = new Deno.Command("stty", {
args: ["-F", "/dev/ttyUSB0", "9600", "cs8", "-cstopb", "-parenb"],
});
await stty.output();Docker Execution Driver
The Docker execution driver uses --allow-all. Deno.open() on device nodes
works when using Docker placement.