Definitions describe reusable behavior. Occurrences say exactly where that behavior is alive. .as
creates another singular occurrence when one definition needs more than one place in a model.
One definition can own several singular sites.
A reusable definition may appear more than once in the same model. Motive gives every appearance a stable site identity without multiplying provider implementations.
1 · Begin with the default site
Every Activity already names one occurrence.
RenderAsset is both a reusable Activity definition and its default singular site. Invoking it mounts exactly one occurrence, with its own input and completion channels, for as long as Rendering owns it.
import { Activity, State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";
export const RenderAsset = Activity.make("RenderAsset", {
input: Schema.Struct({ asset: Schema.Literals(["hero", "thumbnail"]) }),
success: Schema.Void,
});
export class Artwork extends State.Compound<Artwork>()("Artwork") {
static states = States.make(() => [Rendering]);
}
class Rendering extends State.Atomic<Rendering>()("Rendering") {
static transitions = Transitions.make(this, ({ invoke, on }) => [
invoke(RenderAsset, () => ({ asset: "hero" })),
on(RenderAsset.Done),
]);
}
export const ArtworkChart = Statechart.make(Artwork);
2 · Name another singular site
.as adds identity, not another implementation.
Thumbnail is a second exact-one occurrence of RenderAsset. The two sites receive independent inputs and publish independent Done events, while one RenderAsset provider remains the only handler authority.
import { Activity, State, Statechart, States, Transitions } from "@motive/motive";
import { Schema } from "effect";
export const RenderAsset = Activity.make("RenderAsset", {
input: Schema.Struct({ asset: Schema.Literals(["hero", "thumbnail"]) }),
success: Schema.Void,
});
export const Thumbnail = RenderAsset.as("Thumbnail");
export class Artwork extends State.Compound<Artwork>()("Artwork") {
static states = States.make(() => [Rendering]);
}
class Rendering extends State.Atomic<Rendering>()("Rendering") {
static transitions = Transitions.make(this, ({ invoke, on }) => [
invoke(RenderAsset, () => ({ asset: "hero" })),
invoke(Thumbnail, () => ({ asset: "thumbnail" })),
on(RenderAsset.Done),
on(Thumbnail.Done),
]);
}
export const ArtworkChart = Statechart.make(Artwork);
A definition is already its default site
An Activity value has two closely related roles. RenderAsset defines the input, success, error,
and provider contract for rendering an asset. The same value is also the default site accepted by
invoke:
invoke(RenderAsset, () => ({ asset: "hero" }));
That declaration mounts one RenderAsset occurrence while Rendering is active. Its input,
lifecycle, attempts, and terminal channels belong to that occurrence—not to every use of the
definition everywhere.
.as names another exact-one site
Call .as when the same definition needs another singular place in the same declaring state:
const Thumbnail = RenderAsset.as("Thumbnail");
Thumbnail is a site, not a second Activity. It retains RenderAsset's schemas and accepts the
same input, but it owns the occurrence id "Thumbnail" and therefore exposes its own
Thumbnail.Done, Thumbnail.Error, and Thumbnail.Defect channels.
invoke(RenderAsset, () => ({ asset: "hero" })),
invoke(Thumbnail, () => ({ asset: "thumbnail" })),
on(RenderAsset.Done),
on(Thumbnail.Done),
The two invocations can start, retry, settle, and be supervised independently. Completing the hero
does not complete the thumbnail, and handling RenderAsset.Done does not consume
Thumbnail.Done.
One source owns the implementation
Named sites do not create new provider keys. The runtime registers the reusable definition once:
activities: {
[RenderAsset.tag]: {
handler: ({ input: { asset } }) => render(asset),
},
}
Both occurrences call that handler with their own decoded input. A provider entry for Thumbnail
would be a second handler authority, so Motive rejects it. This keeps implementation ownership with
the Activity contract while leaving lifecycle identity with each authored site.
Names are unique where occurrences are declared
Two mounts in one state may not share an occurrence id. Invoking RenderAsset twice repeats its
default id and fails chart assembly with OccurrenceNamesAreSiteUnique. Give every additional
mount a distinct .as(name) instead.
The name must also differ from the source Activity tag: RenderAsset.as("RenderAsset") would
impersonate the default site and is rejected by ActivityAsMustNotUseDefaultId.
Occurrence names are scoped by authored topology. The same named site value may be mounted in two different declaring states because their qualified locations remain distinct. Within one state, the name identifies exactly one mounted lifecycle.
Singular is not a one-member family
.as always declares one exact occurrence. It has one input and one set of terminal channels; it
does not have member keys, captured membership, aggregate settlement, or a family view.
Use a singular site when the model knows the role—Thumbnail, Renderer, Reminder—even if the
same definition fills several such roles. Use a family only when one authored role contains a set
of members whose identities come from data.
The site idea is shared across citizens
Activities are not special here. A Timer definition is its default occurrence and
Deadline.as("Reminder") creates another named timer site. A child chart is its default singular
site and WorkerChart.as("Indexer") creates a named child occurrence. Each citizen keeps its own
runtime contract while .as supplies authored occurrence identity.
Resources also use named provider sites, with additional authority and capacity semantics covered in Resources and Providers. The common reading rule remains: the definition says what can run; the site says which occurrence this declaration owns.
Test occurrence identity
- Invoke the default site and one named site with different inputs; verify one provider handles both.
- Complete either occurrence first; verify only its site-bound
Donelistener fires. - Invoke the default site twice in one state; confirm assembly rejects the duplicate identity.
- Give the additional occurrence a distinct
.as(name); confirm both lifecycles are visible.
Next, .batch turns one authored site into a fixed, keyed family whose membership is captured when
the site arms.