Skip to content

Capabilities

A capability is a typed slot that modules contribute values to and other modules read, so plugins extend each other without importing each other.

A capability is a tag: an NSID plus the TypeScript type of its values. Capability.make declares a multi capability, which any number of modules contribute to. Capability.makeSingleton declares one with exactly one provider.

import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
export type
type Exporter = {
readonly format: string;
readonly export: (text: string) => Uint8Array;
}
Exporter
= {
readonly
format: string
format
: string;
readonly
export: (text: string) => Uint8Array
export
: (
text: string
text
: string) =>
interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>

A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.

Uint8Array
;
};
// A multi capability: any number of modules may contribute one.
export const
const Exporter: Capability.MultiTag<Exporter, "com.example.plugin.export.capability.exporter">
Exporter
=
import Capability
Capability
.
const make: <Exporter>() => <S>(identifier: NsidParam<S>) => Capability.MultiTag<Exporter, S> (+1 overload)
make
<
type Exporter = {
readonly format: string;
readonly export: (text: string) => Uint8Array;
}
Exporter
>()(
'com.example.plugin.export.capability.exporter',
);
// A singleton capability: exactly one module provides it.
export const
const Clock: Capability.Tag<() => Date, "com.example.plugin.export.capability.clock">
Clock
=
import Capability
Capability
.
const makeSingleton: <() => Date>() => <S>(identifier: NsidParam<S>) => Capability.Tag<() => Date, S> (+1 overload)
makeSingleton
<() =>
interface Date

Enables basic storage and retrieval of dates and times.

Date
>()(
'com.example.plugin.export.capability.clock',
);

A module contributes by returning Capability.contribute(tag, value) from its body, and lists the tag in provides. A module requests with yield* on the tag, or with Capability.getAll for every contribution to a multi capability, and lists what it cannot run without in requires.

import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
export const
const ExportMenuModule: Capability.Module<void>
ExportMenuModule
=
import Capability
Capability
.
const inlineModule: <readonly [], readonly [Capability.Tag<() => Date, "com.example.plugin.export.capability.clock">], void, void>(name: string, spec: ModuleSpec<readonly [], readonly [Capability.Tag<() => Date, "com.example.plugin.export.capability.clock">], void, void>, activate: (props: void) => Effect.Effect<void | readonly never[], Error, Capability.Requirements<readonly [Capability.Tag<() => Date, "com.example.plugin.export.capability.clock">]>>) => Capability.Module<void>

Helper to define an eager module body with the same spec-carrying shape as

lazyModule

— for bodies that are plain values (translations, schema) or too small to justify a chunk, so plugin definitions stay a uniform chain of spec-carrying modules.

@param ― name The module name — used to auto-compute module IDs.

@param ― spec The requires/provides declaration.

@param ― activate The module body.

inlineModule
(
'ExportMenu',
{
requires?: readonly [Capability.Tag<() => Date, "com.example.plugin.export.capability.clock">] | undefined
requires
: [
const Clock: Capability.Tag<() => Date, "com.example.plugin.export.capability.clock">
Clock
],
provides: readonly []
provides
: [] },
() =>
import Effect
Effect
.
const gen: <Effect.Effect<() => Date, never, Capability.CapabilityIdentifier<"com.example.plugin.export.capability.clock", "single">> | Effect.Effect<Exporter[], never, Capability.Service>, void>(f: () => Generator<Effect.Effect<() => Date, never, Capability.CapabilityIdentifier<"com.example.plugin.export.capability.clock", "single">> | Effect.Effect<Exporter[], never, Capability.Service>, void, never>) => Effect.Effect<void, never, Capability.CapabilityIdentifier<...> | Capability.Service> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const now: () => Date
now
= yield*
const Clock: Capability.Tag<() => Date, "com.example.plugin.export.capability.clock">
Clock
;
const
const exporters: Exporter[]
exporters
= yield*
import Capability
Capability
.
const getAll: <Exporter>(interfaceDef: Capability.InterfaceDef<Exporter>) => Effect.Effect<Exporter[], never, Capability.Service>

Get all capabilities from the capability manager for a given interface.

@param ― interfaceDef The interface definition of the capability.

@returns ― An array of capability implementations.

getAll
(
const Exporter: Capability.MultiTag<Exporter, "com.example.plugin.export.capability.exporter">
Exporter
);
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@since ― v0.1.100

log
(
const now: () => Date
now
(),
const exporters: Exporter[]
exporters
.
Array<Exporter>.map<string>(callbackfn: (value: Exporter, index: number, array: Exporter[]) => string, thisArg?: any): string[]

Calls a defined callback function on each element of an array, and returns an array that contains the results.

@param ― callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.

@param ― thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.

map
((
exporter: Exporter
exporter
) =>
exporter: Exporter
exporter
.
format: string
format
),
);
}),
);

The capability manager holds every contribution. It is the Capability.Service in a module’s Effect context, and the plugin manager owns it.

  • The compiler checks a module’s body against its provides: a body that returns a contribution the module does not list does not compile.
  • The plugin manager activates a module only after every capability in its requires has a contributor, so a module can read them without waiting.
  • Four ways to read a capability suit different needs:
Call Returns When
Capability.get The first contribution, or fails with CapabilityNotFoundError The capability must be there.
Capability.getAll Every contribution, possibly none A multi capability.
Capability.waitFor The first contribution, once one arrives It is contributed later than this module runs.
Capability.atom An atom of every contribution Reactive code that should update as plugins come and go.
import * as
import Effect
Effect
from 'effect/Effect';
import * as
import ActivationEvents
ActivationEvents
from '@dxos/app-framework/ActivationEvents';
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
import * as
import AppCapabilities
AppCapabilities
from '@dxos/app-toolkit/AppCapabilities';
// `waitFor` suspends until some module contributes the capability, so this
// module can activate on Startup even though App Graph arrives later.
export const
const GraphReadyModule: Capability.Module<void>
GraphReadyModule
=
import Capability
Capability
.
const inlineModule: <readonly [], readonly [], void, void>(name: string, spec: ModuleSpec<readonly [], readonly [], void, void>, activate: (props: void) => Effect.Effect<void | readonly never[], Error, Capability.Requirements<readonly []>>) => Capability.Module<void>

Helper to define an eager module body with the same spec-carrying shape as

lazyModule

— for bodies that are plain values (translations, schema) or too small to justify a chunk, so plugin definitions stay a uniform chain of spec-carrying modules.

@param ― name The module name — used to auto-compute module IDs.

@param ― spec The requires/provides declaration.

@param ― activate The module body.

inlineModule
(
'GraphReady',
{
provides: readonly []
provides
: [],
activatesOn?: Events | undefined

Activates when this runtime event fires instead of during the dependency pass.

activatesOn
:
import ActivationEvents
ActivationEvents
.
const Startup: ActivationEvent

Fired when the app is started. Defined in core; see

ActivationEvent$.Startup

.

Startup
},
() =>
import Effect
Effect
.
const gen: <Effect.Effect<GraphBuilder, never, Capability.Service> | Effect.Effect<Atom<AppCapabilities.Schema[]>, never, Capability.Service>, void>(f: () => Generator<Effect.Effect<GraphBuilder, never, Capability.Service> | Effect.Effect<Atom<AppCapabilities.Schema[]>, never, Capability.Service>, void, never>) => Effect.Effect<void, never, Capability.Service> (+1 overload)

Provides a way to write effectful code using generator functions, simplifying control flow and error handling.

When to use

Use when you want to write effectful code that looks and behaves like synchronous code, while still handling asynchronous tasks, errors, and complex control flow such as loops and conditions.

Generator functions work similarly to async/await but keep errors, requirements, and interruption in the Effect type. You can yield* values from effects and return the final result at the end.

Example (Sequencing effects with generators)

import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = (
total: number,
discountRate: number
): Effect.Effect<number, DiscountRateError> =>
discountRate === 0
? Effect.fail(new DiscountRateError())
: Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() {
const transactionAmount = yield* fetchTransactionAmount
const discountRate = yield* fetchDiscountRate
const discountedAmount = yield* applyDiscount(
transactionAmount,
discountRate
)
const finalAmount = addServiceCharge(discountedAmount)
return `Final amount to charge: ${finalAmount}`
})
await Effect.runPromise(program) // => "Final amount to charge: 96"

@stability ― stable

@category ― constructors

@since ― 2.0.0

gen
(function* () {
const
const graph: GraphBuilder
graph
= yield*
import Capability
Capability
.
const waitFor: <GraphBuilder>(interfaceDef: Capability.InterfaceDef<GraphBuilder>) => Effect.Effect<GraphBuilder, never, Capability.Service>

Wait for a capability to be available.

@param ― interfaceDef The interface definition of the capability.

@returns ― The capability implementation once available.

waitFor
(
import AppCapabilities
AppCapabilities
.
const AppGraph: Capability.Tag<GraphBuilder, "org.dxos.app-framework.capability.appGraph">

The graph builder instance. Its graph is the reactive node tree; the builder also carries the urlKey declarations and node→extension provenance that URL resolution (@dxos/app-graph's path-resolution.ts) reads and reverse-maps — neither derivable from graph alone.

@category ― Capability

AppGraph
);
// An atom of every contribution, for reactive code.
const
const schemas: Atom<AppCapabilities.Schema[]>
schemas
= yield*
import Capability
Capability
.
const atom: <AppCapabilities.Schema>(interfaceDef: Capability.InterfaceDef<AppCapabilities.Schema>) => Effect.Effect<Atom<AppCapabilities.Schema[]>, never, Capability.Service>

Get the Atom reference to capabilities for reactive access.

@param ― interfaceDef The interface definition of the capability.

@returns ― An Atom containing the array of capability implementations.

atom
(
import AppCapabilities
AppCapabilities
.
const Schema: Capability.MultiTag<AppCapabilities.Schema, "org.dxos.app-framework.capability.schema">

@category ― Capability

Schema
);
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@since ― v0.1.100

log
(typeof
const graph: GraphBuilder
graph
,
const schemas: Atom<AppCapabilities.Schema[]>
schemas
);
}),
);
  • One plugin extends another by contributing to a capability the other reads. Stash adds itself to the Space plugin’s create menu this way: the Space plugin declares SpaceCapabilities.CreateObjectEntry and lists every contribution in its menu, and Stash contributes one entry.
src/capabilities/create-object.ts
import * as
import Effect
Effect
from 'effect/Effect';
import * as
import Capability
Capability
from '@dxos/app-framework/Capability';
import * as
import Operation
Operation
from '@dxos/compute/Operation';
import {
import Type
Type
} from '@dxos/echo';
import * as
import SpaceCapabilities
SpaceCapabilities
from '@dxos/plugin-space/SpaceCapabilities';
import * as
import SpaceOperation
SpaceOperation
from '@dxos/plugin-space/SpaceOperation';
import {
class StashedLink
StashedLink
,
const make: (props?: Partial<MakeProps<typeof StashedLink>>) => StashedLink
make
} from '../types/StashedLink.ts';
export default
import Capability
Capability
.
const makeModule: <void, Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>, never, never>(fn: (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>, never, Scope>) => (props: void) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>, never, Scope>

Helper to define a capability module with explicit typing. Wraps the default export function to provide better type inference and make the pattern explicit.

This helper provides explicit typing for the module activation function, making it clear that the function should:

  • Access declared requires via yield* (or the ambient Capability.Service/Plugin.Service)
  • Return an array of typed

Contribution

s (see

contribute

/

contributeAll

)

@example

// Module without options - single capability
export default Capability.makeModule(
Effect.fnUntraced(function* () {
const client = yield* ClientCapabilities.Client;
return Capability.contribute(Capabilities.SettingsStore, store);
})
);
// Module with multiple capabilities
export default Capability.makeModule(
Effect.fnUntraced(function* () {
return [
Capability.contribute(Capabilities.SettingsStore, store),
Capability.contribute(Capabilities.Translations, translations),
];
})
);
// Module with required options (context accessed via layer)
export default Capability.makeModule(
Effect.fnUntraced(function* (props: { observability: boolean }) {
const invoker = yield* Capabilities.OperationInvoker;
return Capability.contribute(Capabilities.OperationHandler, ...);
})
);
// Module with scoped resources (closed automatically on deactivation)
export default Capability.makeModule(
Effect.fnUntraced(function* () {
const scope = yield* Scope.Scope;
yield* Scope.addFinalizer(scope, Effect.sync(() => cleanup()));
return Capability.contribute(Capabilities.MyCapability, implementation);
})
);

makeModule
(() =>
import Effect
Effect
.
const succeed: <Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>>(value: Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>) => Effect.Effect<Capability.Contribution<Capability.CapabilityIdentifier<"org.dxos.plugin.space.capability.createObject", "multi">>, never, never>

Creates an Effect that always succeeds with a given value.

When to use

Use when an effect should complete successfully with a specific value without any errors or external dependencies.

Example (Creating a successful effect)

import { Effect } from "effect"
// Creating an effect that represents a successful scenario
//
// ┌─── Effect<number, never, never>
// ▼
const success = Effect.succeed(42)
Effect.runSync(success) // => 42

@see ― fail to create an effect that represents a failure.

@stability ― stable

@category ― constructors

@since ― 2.0.0

succeed
(
import Capability
Capability
.
const contribute: <Capability.MultiTag<Readonly<{
id: string;
createObject: CreateObject;
inputSchema?: Codec<any, any>;
customPanel?: React.ComponentType<SpaceCapabilities.CreateObjectCustomPanelProps>;
}>, "org.dxos.plugin.space.capability.createObject">>(capability: Capability.MultiTag<Readonly<{
id: string;
createObject: CreateObject;
inputSchema?: Codec<any, any>;
customPanel?: React.ComponentType<SpaceCapabilities.CreateObjectCustomPanelProps>;
}>, "org.dxos.plugin.space.capability.createObject">, implementation: Readonly<...>) => Capability.Contribution<...> (+1 overload)
contribute
(
import SpaceCapabilities
SpaceCapabilities
.
const CreateObjectEntry: Capability.MultiTag<Readonly<{
id: string;
createObject: CreateObject;
inputSchema?: Codec<any, any>;
customPanel?: React.ComponentType<SpaceCapabilities.CreateObjectCustomPanelProps>;
}>, "org.dxos.plugin.space.capability.createObject">

Typed creation entry contributed per typename by plugins that support creating objects.

CreateObjectEntry
, {
id: string
id
:
import Type
Type
.
const getTypename: (input: Type.AnyEntity) => string

@returns ―

The typename. Example: com.example.type.person.

Persisted Type.Type entities carry typename in EntityMeta.key (the canonical registry-provenance field); unnamed drafts fall back to the entity's object id so the helper always returns a string. Any dxn: or echo:/ prefix is stripped — typename is a bare identifier, not a URI.

@performance ― O(1) meta read with jsonSchema and id fallbacks.

getTypename
(
class StashedLink
StashedLink
),
createObject: CreateObject
createObject
: (
props: any
props
,
options: {
db: Database;
target?: Unknown;
targetNodeId?: string;
}
options
) =>
import Operation
Operation
.
const invoke: <{
readonly object: Unknown | {
readonly [x: string]: unknown;
readonly "@type": string;
};
readonly target?: import("@dxos/echo/internal").Ref<Unknown> | Unknown | undefined;
}, {
readonly id: string;
readonly object: Unknown;
}>(op: Operation.Definition<{
readonly object: Unknown | {
readonly [x: string]: unknown;
readonly "@type": string;
};
readonly target?: import("@dxos/echo/internal").Ref<Unknown> | Unknown | undefined;
}, {
readonly id: string;
readonly object: Unknown;
}, any>, input: {
readonly object: Unknown | {
readonly [x: string]: unknown;
readonly "@type": string;
};
readonly target?: import("@dxos/echo/internal").Ref<Unknown> | Unknown | undefined;
}, options?: Operation.InvokeOptions | undefined) => Effect.Effect<...>

Invoke an operation as an Effect. Yields the Operation.Service internally.

@example

yield* Operation.invoke(MyOperation, { data: 'test' });

invoke
(
import SpaceOperation
SpaceOperation
.
const AddObject: Operation.Definition<{
readonly object: Unknown | {
readonly [x: string]: unknown;
readonly "@type": string;
};
readonly target?: import("@dxos/echo/internal").Ref<Unknown> | Unknown | undefined;
}, {
readonly id: string;
readonly object: Unknown;
}, Capability.Service | Service>
AddObject
,
{
object: Unknown | {
readonly [x: string]: unknown;
readonly '@type': string;
}
object
:
function make(props?: Partial<MakeProps<typeof StashedLink>>): StashedLink
make
(
props: any
props
),
target?: Unknown | Ref<Unknown> | undefined
target
:
options: {
db: Database;
target?: Unknown;
targetNodeId?: string;
}
options
.
target?: Unknown | undefined

The created object's parent; absent files at the space root of db.

target
},
{
InvokeOptions.spaceId?: SpaceId | undefined

Space ID to provide database context for the handler.

spaceId
:
options: {
db: Database;
target?: Unknown;
targetNodeId?: string;
}
options
.
db: Database
db
.
Database.spaceId: SpaceId
spaceId
},
),
}),
),
);
  • Contributions change after startup, when a module activates late or a person turns a plugin on or off. Capability.contributions and Capability.atom stay current; a value read once with getAll does not.
  • When a module deactivates, its contributions are removed and its finalizers run.
  • A singleton capability with two providers is an error. Use a multi capability for anything more than one plugin might contribute.
  • The capability manager does not check a value against anything but its TypeScript type. Two plugins that build against different versions of a capability’s type are not caught at runtime.
  • Capabilities are in-process. They do not cross from the app to a worker or another device.