BEHAVIOR
A behavior type is a TypeScript module that automates resource lifecycle
operations. It declares 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/behaviors/ and
exports a behavior object.
For the YAML definition layer that configures behavior instances, see Behavior Definitions.
Export Shape
import { z } from "npm:zod@4";
export const behavior = {
type: "@mycollective/my-behavior",
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 behavior 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 behavior's type identifier. |
modelId |
string |
Instance ID of the behavior 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 } |
Behavior 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 behavior's data by name. |
queryData |
(predicate, select?) => Promise<DataRecord[] | unknown[]> |
Query data artifacts across all behaviors 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 behavior 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"',
);Behavior Invocation
| Field | Signature | Description |
|---|---|---|
runModel |
(options: RunModelOptions) => Promise<RunModelResult> |
Invoke another behavior'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 behavior 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 behavior'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 behavior types and user-authored behavior 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 behavior cannot invoke itself
directly or transitively), and a ceiling of 100 total invocations. Vault secrets
are isolated per extension — the invoked behavior cannot access the caller's
vault bindings.
Data written by the invoked behavior is owned by the target behavior'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 behavior 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 behaviors on workers, chain behavior 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 behavior 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 behavior'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 behavior methods.
MethodResult
Returned by execute to report what the method produced.
| Field | Type | Required | Description |
|---|---|---|---|
dataHandles |
DataHandle[] |
No | Handles to resources or files written. |
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 behavior'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. |
swamp behavior validate and the check before each workflow step apply the same
upgrades to an in-memory copy, which is never saved, and check the upgraded
global arguments. An upgradeAttributes that throws fails the
Global arguments item. See
Upgrades during validation.
A workflow step therefore runs the chain twice, so upgradeAttributes must be
pure: return the new attributes and do nothing else.
An instance whose typeVersion is behind with no entry covering the gap is
reported by swamp behavior get as stranded, and nothing migrates it. See
Staleness.
Packaging
Declare the behavior in your extension's manifest:
behaviors:
- my_behavior.tsThe file path is relative to the extensions/behaviors/ directory within the
extension root. swamp extension push packs the file under models/ in the
archive; see
Manifest § Archive Structure.
Compatibility
The model spelling of each authoring name is still accepted:
| Behavior spelling | Compatibility spelling |
|---|---|
export const behavior |
export const model |
extensions/behaviors/ |
extensions/models/ |
Manifest key behaviors: |
Manifest key models: |
SWAMP_BEHAVIORS_DIR |
SWAMP_MODELS_DIR |
export const model takes the same object as export const behavior. A module
exports one or the other. An export const behavior binding counts as the entry
point only when its object has both type and methods; any other binding of
that name is ignored. A module that exports both as definitions fails to load
with:
Module exports both 'model' and 'behavior'; export only one of them. 'behavior' is another spelling of 'model'.Swamp loads extensions/models/ and, beside it, extensions/behaviors/. When
modelsDir in .swamp.yaml, SWAMP_MODELS_DIR, or SWAMP_BEHAVIORS_DIR sets
the directory, only that directory is loaded. For the models: manifest key,
see
Manifest § Compatibility.
Swamp clients and workers older than the release that introduced the behavior
spelling read only export const model, extensions/models/, and models:.
swamp extension push and swamp extension quality report an extension that
uses the behavior spelling with a behavior-spelling warning.
TypeScript API names keep the model spelling: context.runModel(),
RunModelOptions, RunModelResult, context.readModelData(),
context.modelType, context.modelId, and dataRepository.findAllForModel().
There is no behavior-spelled alias for these.
Runtime Permissions
Extension behavior 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();To reach a device attached to a particular host, run the step on a worker
deployed on that host, select it with
placement labels,
and use Deno.Command as above.