# Cosmonic > Documentation for Cosmonic Desktop and Cosmonic Control: capability-driven WebAssembly sandboxes for your agent's code, from laptop to Kubernetes This file contains all documentation content in a single document following the llmstxt.org standard. ## Cosmonic Control API Reference ## API Reference ### Packages - [control.cosmonic.io/v1alpha2](#controlcosmoniciov1alpha2) - [control.cosmonic.io/v1alpha1](#controlcosmoniciov1alpha1) ### control.cosmonic.io/v1alpha2 Package v1alpha2 contains API Schema definitions for Cosmonic Control v1alpha2 API group. #### Resource Types - [HTTPTrigger](#httptrigger) - [HTTPTriggerList](#httptriggerlist) ##### HTTPTrigger HTTPTrigger routes HTTP traffic to WorkloadDeployments it references by name. Its spec is shaped like Gateway API's HTTPRoute. _Appears in:_ - [HTTPTriggerList](#httptriggerlist) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `control.cosmonic.io/v1alpha2` | | | | `kind` _string_ | `HTTPTrigger` | | | | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `spec` _[HTTPTriggerSpec](#httptriggerspec)_ | | | | ##### HTTPTriggerList HTTPTriggerList contains a list of HTTPTrigger. | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `control.cosmonic.io/v1alpha2` | | | | `kind` _string_ | `HTTPTriggerList` | | | | `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `items` _[HTTPTrigger](#httptrigger) array_ | | | | ##### HTTPTriggerMatch HTTPTriggerMatch mirrors gatewayv1.HTTPRouteMatch. Only path matching is supported so far; header, query, and method matches will use HTTPRoute's shapes when added. _Appears in:_ - [HTTPTriggerRule](#httptriggerrule) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `path` _[HTTPPathMatch](https://gateway-api.sigs.k8s.io/references/spec/#httppathmatch)_ | | \{ type:PathPrefix value:/ \} | | ##### HTTPTriggerRule HTTPTriggerRule mirrors gatewayv1.HTTPRouteRule's matches, timeouts, and backendRefs. _Appears in:_ - [HTTPTriggerSpec](#httptriggerspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `matches` _[HTTPTriggerMatch](#httptriggermatch) array_ | Matches select the requests this rule applies to. Any match is enough. | \[\{ path:\{ type:PathPrefix value:/ \} \}\] | MaxItems: 64 | | `timeouts` _[HTTPRouteTimeouts](https://gateway-api.sigs.k8s.io/references/spec/#httproutetimeouts)_ | Timeouts bound requests matching this rule. | | | | `backendRefs` _[WorkloadBackendRef](#workloadbackendref) array_ | BackendRefs are the WorkloadDeployments matching requests are sent to,split by weight.When a WorkloadDeployment sets `config.host` on its wasi:http interface,the Host header is rewritten to that value for every backend in therule, including backends that set no `config.host`. The client'soriginal host is kept in X-Forwarded-Host. Backends with weight thatname different hosts cannot share a rule. | | MaxItems: 16 MinItems: 1 | ##### HTTPTriggerSpec HTTPTriggerSpec mirrors gatewayv1.HTTPRouteSpec without ParentRefs: a trigger attaches to Control's HTTP listener implicitly. _Appears in:_ - [HTTPTrigger](#httptrigger) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `hostnames` _[Hostname](https://gateway-api.sigs.k8s.io/references/spec/#hostname) array_ | Hostnames are the Host header values this trigger matches, withHTTPRoute's semantics. When empty, the trigger matches every host. | | MaxItems: 16 | | `rules` _[HTTPTriggerRule](#httptriggerrule) array_ | Rules route matching requests to WorkloadDeployments. | | MaxItems: 16 MinItems: 1 | ##### ParentReference ParentReference has gatewayv1.ParentReference's JSON shape. _Appears in:_ - [RouteParentStatus](#routeparentstatus) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `group` _[Group](https://gateway-api.sigs.k8s.io/references/spec/#group)_ | | | | | `kind` _[Kind](https://gateway-api.sigs.k8s.io/references/spec/#kind)_ | | | | | `namespace` _[Namespace](https://gateway-api.sigs.k8s.io/references/spec/#namespace)_ | | | | | `name` _[ObjectName](https://gateway-api.sigs.k8s.io/references/spec/#objectname)_ | | | | | `sectionName` _[SectionName](https://gateway-api.sigs.k8s.io/references/spec/#sectionname)_ | | | | | `port` _integer_ | | | Maximum: 65535 Minimum: 1 | ##### RouteParentStatus RouteParentStatus has gatewayv1.RouteParentStatus's JSON shape. It's copied because the pinned controller-gen can't generate gatewayv1.ParentReference, whose Port is a type alias. _Appears in:_ - HTTPTriggerStatus, the `status` of an [HTTPTrigger](#httptrigger) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `parentRef` _[ParentReference](#parentreference)_ | | | | | `controllerName` _[GatewayController](https://gateway-api.sigs.k8s.io/references/spec/#gatewaycontroller)_ | | | | | `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#condition-v1-meta) array_ | | | MaxItems: 8 | ##### TriggerBackendStatus TriggerBackendStatus reports one WorkloadDeployment backend. _Appears in:_ - HTTPTriggerStatus, the `status` of an [HTTPTrigger](#httptrigger) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | | | | | `weight` _integer_ | Weight is the backend's weight summed across the rules that reference it. | | | | `readyWorkloads` _integer_ | ReadyWorkloads counts the backend's Workloads traffic can be sent to. | | | ##### WorkloadBackendRef WorkloadBackendRef mirrors gatewayv1.HTTPBackendRef, narrowed to a WorkloadDeployment in the trigger's namespace. _Appears in:_ - [HTTPTriggerRule](#httptriggerrule) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `group` _string_ | | runtime.wasmcloud.dev | Enum: [runtime.wasmcloud.dev] | | `kind` _string_ | | WorkloadDeployment | Enum: [WorkloadDeployment] | | `name` _string_ | Name of a WorkloadDeployment in the trigger's namespace. | | MaxLength: 253 MinLength: 1 | | `weight` _integer_ | Weight is this backend's share of the rule's traffic, relative to theother backends. Zero sends it nothing. | 1 | Maximum: 1e+06 Minimum: 0 | ### control.cosmonic.io/v1alpha1 Package v1alpha1 contains API Schema definitions for Cosmonic Control v1alpha1 API group. `HTTPTrigger` is no longer served in `v1alpha1` as of Cosmonic Control v0.13.0, so its `v1alpha1` types are not listed here. The version stays in the CRD, unserved, only because clusters that ran it record it in `status.storedVersions`. Use [`control.cosmonic.io/v1alpha2`](#controlcosmoniciov1alpha2), and see [Upgrading from v0.12.2 to v0.13.0](/operations/upgrading#httptrigger-v1alpha2-references-a-workloaddeployment) to migrate. `ProjectEnvironment` is unchanged and still served as `v1alpha1`. #### Resource Types - [ProjectEnvironment](#projectenvironment) - [ProjectEnvironmentList](#projectenvironmentlist) ##### ProjectEnvironment ProjectEnvironment defines the team membership and roles (RBAC) for a Kubernetes namespace managed as a Cosmonic Control project. _Appears in:_ - [ProjectEnvironmentList](#projectenvironmentlist) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `control.cosmonic.io/v1alpha1` | | | | `kind` _string_ | `ProjectEnvironment` | | | | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `spec` _[ProjectEnvironmentSpec](#projectenvironmentspec)_ | | | | ##### ProjectEnvironmentGroupSpec _Appears in:_ - [ProjectEnvironmentSpec](#projectenvironmentspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | | | | | `role` _[ProjectEnvironmentRole](#projectenvironmentrole)_ | | | | ##### ProjectEnvironmentList ProjectEnvironmentList contains a list of ProjectEnvironment. | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `control.cosmonic.io/v1alpha1` | | | | `kind` _string_ | `ProjectEnvironmentList` | | | | `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `items` _[ProjectEnvironment](#projectenvironment) array_ | | | | ##### ProjectEnvironmentRole _Underlying type:_ _string_ _Appears in:_ - [ProjectEnvironmentGroupSpec](#projectenvironmentgroupspec) | Field | Description | | --- | --- | | `owner` | | | `admin` | | | `member` | | | `viewer` | | ##### ProjectEnvironmentSpec ProjectEnvironmentSpec defines the desired state of ProjectEnvironment. _Appears in:_ - [ProjectEnvironment](#projectenvironment) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `groups` _[ProjectEnvironmentGroupSpec](#projectenvironmentgroupspec) array_ | | | Optional: \{\} | --- ## cosmonic:kafka `cosmonic:kafka@0.5.0` is the Kafka capability **built into the Cosmonic Control host**. A `hostPlugins` entry for it names no `image` or `file`, and there is no provider to deploy. For how to enable and configure the plugin on a host group, see [the `cosmonic:kafka` host plugin](/operations/host-groups#the-cosmonickafka-host-plugin). This page is the interface reference. The WIT is fetched from the digest-pinned `ghcr.io/cosmonic-labs/cosmonic/kafka` package. ### Bindings are capabilities A guest never supplies a broker, credential, group, or arbitrary client property to a WIT function. The workload's resolved host-interface binding supplies all of them before the component runs. That is why `consumer.open()` takes no arguments and why there is no `producer` resource to construct. A plain import uses the unnamed binding; a **named import**, written with `(implements ...)`, selects a named binding and is the explicit way to grant another cluster, principal, group, or client policy. A provider retains interface declarations **per binding**. Unioning every `cosmonic:kafka` interface across a component would let a named import reach an interface declared only on another binding. ### Choosing an interface | Interface | Use it for | Shape | |---|---|---| | `producer` | Publishing | Binding-scoped functions, no resource | | `transaction` | Atomic publishing, and offset commits alongside them | `begin()` returns a stateful resource | | `consumer` | A long-lived service needing direct control of a Kafka session | A stateful resource your component owns | | `handler` | Event-driven processing that scales to zero | Your component **exports** it; the host polls | | `types` | Shared vocabulary | Records, variants, and the `error-code` variant | **`handler` is the serverless path and `consumer` is the service path.** With `handler`, the provider owns polling, heartbeats, assignment, retries, and commits, and invokes your component only when a partition has work. That means growing a component pool does not add Kafka group members. With `consumer`, your component owns group membership and must keep reading the record stream, because the heartbeat is driven by that stream: a consumer whose stream is not being read is eventually evicted from its group, the async equivalent of exceeding `max.poll.interval.ms`. ### Binding configuration Configuration resolves from `config`, `configFrom` and `secretFrom` under the host's normal layering rules. Broker addresses and non-secret policy go in `config`; credentials go in a Secret named by `secretFrom`. Unknown or malformed properties fail binding rather than being ignored, because silently dropping a durability, authentication or isolation property would change the contract. Across all shapes, **`topics` is the authorization ceiling** for every topic operation on the binding. #### Ordinary producer ```yaml hostInterfaces: - namespace: cosmonic package: kafka version: "0.5.0" interfaces: [producer] config: bootstrap.servers: kafka.svc:9092 topics: orders.created,orders.updated acks: all compression.type: zstd secretFrom: - name: kafka-orders-credentials ``` The imported interface *is* the producer capability. There is no `open`, `close` or global `flush`. Each send reports its own delivery outcome, and the provider keeps the shared native client for the binding's lifetime. #### Transactional producer ```yaml hostInterfaces: - name: orders-tx namespace: cosmonic package: kafka version: "0.5.0" interfaces: [transaction] config: bootstrap.servers: kafka.svc:9092 topics: orders.input,orders.output transactional.id: order-processor-0 transaction.group.id: order-processor ``` `transactional.id` is required and **must stay stable across restarts** for Kafka fencing and recovery. `transaction.group.id` is required only if the workload calls `send-offsets`. A binding must not grant both `producer` and `transaction` over the same native producer: declare separate bindings when you need both. And every simultaneously live replica needs its own stable transactional ID; reusing one across hosts or replicas causes Kafka to fence one of them. Elastic transaction pools therefore need an external stable-ID allocation scheme, which this interface does not provide. #### Host-driven handler ```yaml hostInterfaces: - namespace: cosmonic package: kafka version: "0.5.0" interfaces: [handler, producer] config: bootstrap.servers: kafka.svc:9092 topics: orders.input,orders.output,orders.dlq handler.topics: orders.input handler.group.id: order-transformer handler.batch.size: "32" dead-letter.topic: orders.dlq ``` `handler.topics` is the subscription; `topics` is the ceiling. The two differ because a handler usually reads one topic and produces to another. `handler.group.id` is the provider-owned consumer group, `handler.batch.size` caps the records dispatched in one call, and `dead-letter.topic` receives permanent failures; it must itself sit inside the `topics` grant. `handler.topics` is the gate, not one of the checks. **A binding that omits it binds successfully and is silently never dispatched to**, on the reasoning that a component exporting `handler` without asking for dispatch is a library world rather than a mistake. The only trace is a debug log. A typo there produces a green deploy that processes nothing, so check it first when a handler looks idle. Once `handler.topics` is present, omitting `bootstrap.servers`, `handler.group.id` or `dead-letter.topic` does fail the bind, as does a transaction binding without `transactional.id`. A pull consumer without `consumer.group.id` stays valid for manual assignment, but its `subscribe` call fails. Handler config is read only from the **unnamed** binding, and only one handler component per workload is permitted. ### `producer` Every function is `async`: a host component plugin installs concurrent host functions on the caller's linker, and a synchronous function cannot be served that way. | Function | Signature | |---|---| | `send` | `(topic: string, %record: produce-record) -> result` | | `send-batch` | `(topic: string, records: list) -> result>, error>` | | `send-stream` | `(topic: string, records: stream) -> future>` | | `partition-count` | `(topic: string) -> result` | | `watermark-offsets` | `(topic: string, partition: partition) -> result` | `send-batch` results are **positional**: entry N is the outcome for record N. Records are enqueued before their delivery reports are awaited so the provider can batch them, and a record rejected before enqueue appears as an error in its own position without failing unrelated entries. `send-stream` applies backpressure: the provider stops reading while its bounded queues are full. The returned future resolves only after the input ends *and* every accepted record has an outcome. If the workload, binding or provider stops first, the future reports an error, because accepted records may have an unknown outcome. Common errors: `msg-timed-out` (not acknowledged within `message.timeout.ms`), `queue-full` (the binding's bounded local queue), `unknown-topic-or-part` (topic absent, auto-create off), `not-leader-for-partition` (leadership moved; retry after refresh). ### `transaction` `begin()` initializes the binding's transactional client (fencing any previous producer using the same `transactional.id`) and leases its single transaction slot. The initial contract permits **one active transaction per binding**; a concurrent `begin` fails with `state` or a retriable resource error. | Function | Signature | |---|---| | `begin` | `() -> result` | | `partition-count` | `(topic: string) -> result` | | `watermark-offsets` | `(topic: string, partition: partition) -> result` | The `transaction` resource is a **strict state machine**. Producing and `send-offsets` are valid while active. A `commit`, `abort` or dropped resource ends it. Calls after a terminal operation fail with `state`. After an error carrying `txn-requires-abort`, the only valid terminal operation is `abort`. A fatal error retires the binding's native client. | Method | Signature | |---|---| | `send` | `(topic: string, %record: produce-record) -> result` | | `send-batch` | `(topic: string, records: list) -> result>, error>` | | `send-stream` | `(topic: string, records: stream) -> future>` | | `send-offsets` | `(offsets: list) -> result<_, error>` | | `commit` | `() -> result<_, error>` | | `abort` | `() -> result<_, error>` | `send-offsets` takes the positions to *resume at*, normally one past the last record processed. The provider validates every topic and uses the binding's fixed `transaction.group.id`, so a guest cannot select or disturb another consumer group. A concurrent `commit` or `abort` closes stream admission. Permits already granted may finish enqueueing, which defines the accepted prefix: `commit` waits for that prefix and includes it, `abort` waits until nothing can enqueue and discards. The stream future reports `state` when finalization truncates its input, so a caller cannot mistake the prefix for the complete stream. ### `consumer` A stateful resource holding group membership and its heartbeat. Every function here is `async`, as in `producer` and `transaction`. `open` is the only `static` member in the package: `open: static async func() -> result`. It takes no arguments because the binding fixes brokers, credentials, group identity, offset behavior, isolation level, assignment strategy, fetch policy and client limits. Note the provider forces `enable.auto.offset.store=false` and defaults `enable.auto.commit=false`, overriding an operator who sets them. **Subscription and assignment** | Method | Signature | |---|---| | `subscribe` | `(topics: list) -> result<_, error>` | | `unsubscribe` | `() -> result<_, error>` | | `subscription` | `() -> result, error>` | | `assign` | `(partitions: list) -> result<_, error>` | | `incremental-assign` | `(partitions: list) -> result<_, error>` | | `incremental-unassign` | `(partitions: list) -> result<_, error>` | | `assignment` | `() -> result, error>` | | `assignment-lost` | `() -> bool` | | `rebalance-protocol` | `() -> rebalance-protocol` | `subscribe` replaces any previous subscription, and a topic name beginning with `^` is treated as a regular expression. Resolving does not mean partitions are assigned; that arrives on `rebalances` once the group settles. A provider **rejects a regular expression when the binding has a finite literal topic grant**, because comparing the expression text to that list cannot prove which topics it will match. **Reading** | Method | Signature | |---|---| | `records` | `() -> result, future>>, error>` | | `rebalances` | `() -> result, error>` | | `pause` | `(partitions: list) -> result<_, error>` | | `resume` | `(partitions: list) -> result<_, error>` | **`records` and `rebalances` are each callable once.** A second call returns `state`. And calling `rebalances` switches the consumer to **manual assignment**: the provider stops answering rebalances for you, so a guest that takes the stream and never calls `assign` or `incremental-assign` gets no partitions. Rebalance events are dropped with a warning if the guest is not draining them. **Offsets** | Method | Signature | |---|---| | `commit` | `(offsets: list) -> result, error>` | | `committed` | `(partitions: list) -> result, error>` | | `position` | `(partitions: list) -> result, error>` | | `seek` | `(partitions: list, to: position) -> result<_, error>` | | `offsets-for-times` | `(partitions: list, time: timestamp-ms) -> result, error>` | | `watermark-offsets` | `(topic: string, partition: partition) -> result` | | `close` | `() -> result<_, error>` | **Rebalance events.** `rebalance-event` is `assign` (partitions being added: accept with `assign` under the `eager` protocol or `incremental-assign` under `cooperative`), `revoke` (being taken away: commit progress *first*, because once acknowledged those offsets can no longer be committed), or `lost` (the assignment went without a clean revoke, typically after falling out of the group; another member may already own them). `rebalance-protocol` is `none` (not in a group, or not yet joined), `eager` (stop-the-world: the whole assignment is revoked then reassigned), or `cooperative` (only the partitions that actually move are revoked). ### `handler` Your component **exports** this one. The provider imports it and calls in. ```wit handle: async func(records: list) -> result, handler-error>; ``` The batch is never empty, and the return value says **how far it was handled**: - `ok(none)` — the whole batch. The provider commits past its last record. - `ok(some(offset))` — handled through `offset` inclusive and no further. The provider commits past that one and redelivers the rest, so a handler that processes seven of ten records keeps the seven instead of being given all ten again. - `error` — none of it was handled. An offset outside the batch is a caller error and is treated as `error`, since committing past records the provider never delivered would drop them. `handler-error` is deliberately a variant rather than a `types.error`, and the distinction is the one thing the handler knows and the provider cannot infer: - `transient(option)` — try again. A dependency was unavailable, a lock was held, a quota was spent. - `permanent(option)` — this record will never succeed. On a single-record batch it is dead-lettered immediately. **On a multi-record batch the whole batch is rewound and the head record redelivered alone** for its own verdict, because a batch is only ever dead-lettered one record at a time. The batch ceiling also collapses to 1 after any failure and doubles back on success. ### Core types | Type | Fields | |---|---| | `consumed-record` | `topic`, `partition`, `offset`, `key`, `value`, `headers`, `timestamp`, `timestamp-type`, `leader-epoch` | | `produce-record` | `partition`, `key`, `value`, `headers`, `timestamp` | | `produce-ack` | `partition`, `offset`, `timestamp` | | `partition-ref` | `topic`, `partition` | | `partition-offset` | `topic`, `partition`, `offset`, `leader-epoch`, `metadata` | | `watermarks` | `low`, `high` | | `header` | `key`, `value` | Aliases: `timestamp-ms = s64`, `partition = s32`, `offset = s64`. `error` is a **record**, not a bare code: | Field | Type | Meaning | |---|---|---| | `code` | `error-code` | A large variant mirroring the underlying client's taxonomy, including `msg-timed-out`, `queue-full`, `unknown-topic-or-part` and `not-leader-for-partition`. | | `message` | `string` | Human-readable detail. Not stable; do not parse it. | | `fatal` | `bool` | The native client is unusable and must be rebuilt or reopened. | | `retriable` | `bool` | The identical operation may be retried as-is. | | `txn-requires-abort` | `bool` | The in-flight transaction must be aborted before further transactional work. | **Branch on the three predicates, not on `code`.** They are not derivable from it: the same code can be retriable in one context and fatal in another, so they are reported per occurrence. `partition-result`, returned by `commit`, `committed`, `position` and `offsets-for-times`, carries `topic`, `partition`, `offset` (absent when that partition failed), `leader-epoch`, `metadata`, and `error` (absent on success). `position`, the `seek` target, is `beginning`, `end`, `stored`, `tail(u64)` or `exact(offset)`. `timestamp-type` is `not-available`, `create-time` or `log-append-time`. ### Worlds | World | Imports | Exports | |---|---|---| | `producer-client` | `types`, `producer` | — | | `transactional-producer-client` | `types`, `transaction` | — | | `consumer-service` | `types`, `consumer` | `wasi:cli/run@0.3.0` | | `processor-service` | `types`, `consumer`, `transaction` | `wasi:cli/run@0.3.0` | | `record-handler` | `types`, `producer` | `handler` | | `host-plugin` | host interfaces, `types`, `handler` | `types`, `producer`, `transaction`, `consumer`, workload lifecycle | `consumer-service` and `processor-service` export `wasi:cli/run` because they are called once and expected to run for the service lifetime. In `processor-service`, the consumer and transaction bindings must name the **same** consumer group when offsets and produced records need to commit atomically. ### Delivery semantics **Handler delivery is at least once**, and a trap, timeout, stopped workload, provider restart or uncertain handoff can redeliver records. **Handlers must be idempotent.** Offsets usually move only after the handler reports progress, but not always. A `permanent` verdict on a single record is dead-lettered at once and the offset stored past it, and a record that only ever traps is rewound five consecutive times and then dead-lettered the same way. `transient` is deliberately excluded from that cap: it paces redeliveries with a 100 ms to 30 s backoff and retries indefinitely. The provider never drops a record it has nowhere to put. `dead-letter.topic` is required at bind precisely so that case cannot arise, but if a record ever does fail with no dead-letter topic, the partition stops behind it and the provider escalates from a repeating warning to an error an operator can alert on. Calls for different partitions may run concurrently; calls for one partition stay ordered and never overlap. Clients and their queues are bounded host resources. Enforced limits include 64 clients per component per kind, 32 send-streams per component, 64 handler calls in flight, buffered-byte ceilings, and validation of native client configuration. There is no limit on the number of bindings. `send-stream` also applies a per-record size limit of roughly 1 MiB; an oversized record comes back as an `invalid-arg` entry rather than failing the stream. --- ## wasmCloud Runtime Operator API Reference ## API Reference ### Packages - [runtime.wasmcloud.dev/v1alpha1](#runtimewasmclouddevv1alpha1) ### runtime.wasmcloud.dev/v1alpha1 Package v1alpha1 contains API Schema definitions for the runtime v1alpha1 API group. #### Resource Types - [Artifact](#artifact) - [ArtifactList](#artifactlist) - [Host](#host) - [HostList](#hostlist) - [Workload](#workload) - [WorkloadDeployment](#workloaddeployment) - [WorkloadDeploymentList](#workloaddeploymentlist) - [WorkloadList](#workloadlist) - [WorkloadReplicaSet](#workloadreplicaset) - [WorkloadReplicaSetList](#workloadreplicasetlist) ##### Artifact Artifact is the Schema for the artifacts API. _Appears in:_ - [ArtifactList](#artifactlist) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `Artifact` | | | | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `spec` _[ArtifactSpec](#artifactspec)_ | | | | ##### ArtifactList ArtifactList contains a list of Artifact. | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `ArtifactList` | | | | `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `items` _[Artifact](#artifact) array_ | | | | ##### ArtifactSpec ArtifactSpec defines the desired state of Artifact. _Appears in:_ - [Artifact](#artifact) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `image` _string_ | | | Required: \{\} | | `imagePullSecret` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#localobjectreference-v1-core)_ | | | Optional: \{\} | ##### ConfigLayer _Appears in:_ - [HostInterface](#hostinterface) - [LocalResources](#localresources) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `configFrom` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#localobjectreference-v1-core) array_ | ConfigFrom is a list of references to ConfigMaps that will be provided to the workload.The keys and values of all referenced ConfigMaps will be merged. In case of key conflicts,the last ConfigMap in the list wins. | | Optional: \{\} | | `secretFrom` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#localobjectreference-v1-core) array_ | The keys and values of all referenced Secrets will be merged. In case of key conflicts,the last Secret in the list wins.The values of the Secrets will be base64-decoded, utf-8 decoded before being provided to the workload. | | Optional: \{\} | | `config` _object (keys:string, values:string)_ | | | Optional: \{\} | ##### EphemeralVolume EphemeralVolume represents a temporary directory that shares a workload's lifetime. _Appears in:_ - [Volume](#volume) ##### Host Host is the Schema for the Hosts API. _Appears in:_ - [HostList](#hostlist) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `Host` | | | | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `hostId` _string_ | | | Required: \{\} | | `hostname` _string_ | | | Optional: \{\} | | `httpPort` _integer_ | | | Optional: \{\} | | `environment` _string_ | Environment records where the host is running. For Kubernetes hostpods this is the pod's namespace; for out-of-cluster hosts it can beany operator-defined identifier (e.g. a region or data center). | | Optional: \{\} | ##### HostInterface _Appears in:_ - [WorkloadSpec](#workloadspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `configFrom` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#localobjectreference-v1-core) array_ | ConfigFrom is a list of references to ConfigMaps that will be provided to the workload.The keys and values of all referenced ConfigMaps will be merged. In case of key conflicts,the last ConfigMap in the list wins. | | Optional: \{\} | | `secretFrom` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#localobjectreference-v1-core) array_ | The keys and values of all referenced Secrets will be merged. In case of key conflicts,the last Secret in the list wins.The values of the Secrets will be base64-decoded, utf-8 decoded before being provided to the workload. | | Optional: \{\} | | `config` _object (keys:string, values:string)_ | | | Optional: \{\} | | `name` _string_ | Name uniquely identifies this interface instance when multiple entriesshare the same namespace+package. It is the `(implements )` id thehost uses to route a component's named import to this interface's backend,so two imports of the same namespace:package can resolve to differentbackends.Required when multiple entries of the same namespace:package exist. | | MaxLength: 64 Optional: \{\} Pattern: `^[a-z0-9][a-z0-9-]*$` | | `namespace` _string_ | | | MaxLength: 128 Required: \{\} | | `package` _string_ | | | MaxLength: 128 Required: \{\} | | `interfaces` _string array_ | | | MinItems: 1 Required: \{\} | | `version` _string_ | | | MaxLength: 64 Optional: \{\} | ##### HostList HostList contains a list of Host. | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `HostList` | | | | `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `items` _[Host](#host) array_ | | | | ##### HostPathVolume HostPathVolume represents a pre-existing file or directory on the host machine. _Appears in:_ - [Volume](#volume) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `path` _string_ | Path of the file or directory on the host. | | Required: \{\} | ##### KubernetesServiceRef KubernetesServiceRef references an existing Kubernetes Service that the operator will manage an EndpointSlice for, pointing to the host pods that are running this workload. _Appears in:_ - [KubernetesSpec](#kubernetesspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | Name is the name of the Kubernetes Service in the same namespace. | | Required: \{\} | ##### KubernetesSpec KubernetesSpec groups Kubernetes-specific configuration for a workload. _Appears in:_ - [WorkloadDeploymentSpec](#workloaddeploymentspec) - [WorkloadSpec](#workloadspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `service` _[KubernetesServiceRef](#kubernetesserviceref)_ | Service references an existing Kubernetes Service that the operator willmaintain an EndpointSlice for, pointing to the host pods running thisworkload. When set, the operator also registers DNS aliases for theservice (e.g. service-name, service-name.namespace.svc.cluster.local)with the host so cluster-internal callers can reach the workload viaService DNS without going through an external gateway. | | Optional: \{\} | ##### LocalResources LocalResources describes resources that will be made available to a workload component. _Appears in:_ - [WorkloadComponent](#workloadcomponent) - [WorkloadService](#workloadservice) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `volumeMounts` _[VolumeMount](#volumemount) array_ | VolumeMounts is a list of volume mounts that will be mounted into the workload component.The volumes must be defined in the WorkloadSpec.Volumes field. | | Optional: \{\} | | `environment` _[ConfigLayer](#configlayer)_ | | | Optional: \{\} | | `config` _object (keys:string, values:string)_ | | | Optional: \{\} | | `allowedHosts` _string array_ | AllowedHosts is the outbound egress allowlist for this component.Each entry must match one of: - "*" (allow all) - "host[:port]" e.g. "example.com" or "example.com:8443" - "scheme://host[:port][/]" e.g. "https://api.example.com" or "https://api.example.com/" - "*.suffix[:port]" e.g. "*.example.com" or "*.example.com:8443" - "scheme://*.suffix[:port][/]" e.g. "https://*.example.com"This is a hosts policy, not a URL policy: entries must not include apath (beyond bare `/`), query string, or fragment. The wildcard mustbe `*.` (leading dot required); a bare `*foo` is rejected.Empty or absent allowedHosts denies all outgoing requests(fail-closed). To opt into unrestricted egress, set `allowedHosts:
["*"]` explicitly. Final validation runs in the runtime. This regexis an admission-time guard, not the source of truth. | | Optional: \{\} items:Pattern: `^\*$\|^([A-Za-z][A-Za-z0-9+.-]*://)(\*\.)?[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*(:[0-9]{1,5})?/?$\|^(\*\.)?[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*(:[0-9]{1,5})?$` | | `allowedIpNameLookups` _string array_ | AllowedIPNameLookups is the set of names this component may resolvethrough wasi:sockets/ip-name-lookup (resolve-addresses).Each entry must match one of: - "*" (resolve any name) - "*.suffix" e.g. "*.wasmcloud.io" - "host" e.g. "api.wasmcloud.io" - a literal IP address, e.g. "10.0.0.1" or "::1"A name is not a URL: entries must not carry a scheme, port, path,query string, or fragment. The wildcard must be "*.<rest>" with aleading dot; a bare "*foo" is rejected.Empty or absent allowedIpNameLookups denies every lookup, reported tothe component as permanent-resolver-failure. To resolve any name,set allowedIpNameLookups: ["*"] explicitly. Resolution is grantedseparately from allowedHosts, which governs outbound connectionsrather than name lookups. Final validation runs in the runtime.This regex is an admission-time guard, not the source of truth. | | Optional: \{\} items:Pattern: `^\*$\|^(\*\.)?[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$\|^[0-9A-Fa-f:.]+$` | ##### ReplicaSetStatus _Appears in:_ - [WorkloadDeploymentStatus](#workloaddeploymentstatus) - [WorkloadReplicaSetStatus](#workloadreplicasetstatus) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `expected` _integer_ | | | Optional: \{\} | | `current` _integer_ | | | Optional: \{\} | | `ready` _integer_ | | | Optional: \{\} | | `unavailable` _integer_ | | | Optional: \{\} | ##### Volume Volume represents a named volume that can be mounted by components. _Appears in:_ - [WorkloadSpec](#workloadspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | Name of the volume. Must be a DNS_LABEL and unique within the Workload. | | Required: \{\} | | `ephemeral` _[EphemeralVolume](#ephemeralvolume)_ | EphemeralVolume represents a temporary directory that shares a workload's lifetime. | | Optional: \{\} | | `hostPath` _[HostPathVolume](#hostpathvolume)_ | HostPathVolume represents a pre-existing file or directory on the host machine. | | Optional: \{\} | ##### VolumeMount VolumeMount describes a mounting of a Volume within a component. _Appears in:_ - [LocalResources](#localresources) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | Name must match the Name of a Volume defined in the WorkloadSpec.Volumes field. | | Required: \{\} | | `mountPath` _string_ | MountPath is the path within the component where the volume should be mounted. | | Required: \{\} | | `readOnly` _boolean_ | ReadOnly indicates whether the volume should be mounted as read-only. | | Optional: \{\} | ##### Workload Workload is the Schema for the artifacts API. _Appears in:_ - [WorkloadList](#workloadlist) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `Workload` | | | | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `spec` _[WorkloadSpec](#workloadspec)_ | | | | ##### WorkloadComponent WorkloadComponent represents a component of a workload. Components are stateless, invocation-driven units of computation. Components are isolated from each other and can be scaled independently. Each Component has a Root WIT World, representing the Components imports/exports. The combined list of all Components' Root WIT Worlds within a workload must be compatible with the Host's WIT World. All components within a workload are guaranteed to be placed on the same Wasm Host. _Appears in:_ - [WorkloadSpec](#workloadspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | | | Required: \{\} | | `image` _string_ | | | Required: \{\} | | `imagePullSecret` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#localobjectreference-v1-core)_ | | | Optional: \{\} | | `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#pullpolicy-v1-core)_ | | | Optional: \{\} | | `poolSize` _integer_ | | | Optional: \{\} | | `maxInvocations` _integer_ | | | Optional: \{\} | | `localResources` _[LocalResources](#localresources)_ | | | Optional: \{\} | ##### WorkloadDeployPolicy _Underlying type:_ _string_ _Appears in:_ - [WorkloadDeploymentSpec](#workloaddeploymentspec) | Field | Description | | --- | --- | | `RollingUpdate` | | | `Recreate` | | ##### WorkloadDeployment WorkloadDeployment is the Schema for the artifacts API. _Appears in:_ - [WorkloadDeploymentList](#workloaddeploymentlist) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `WorkloadDeployment` | | | | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `spec` _[WorkloadDeploymentSpec](#workloaddeploymentspec)_ | | | | ##### WorkloadDeploymentArtifact _Appears in:_ - [WorkloadDeploymentSpec](#workloaddeploymentspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `name` _string_ | | | Required: \{\} | | `artifactFrom` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#localobjectreference-v1-core)_ | | | Required: \{\} | ##### WorkloadDeploymentList WorkloadDeploymentList contains a list of HttpTrigger. | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `WorkloadDeploymentList` | | | | `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `items` _[WorkloadDeployment](#workloaddeployment) array_ | | | | ##### WorkloadDeploymentSpec WorkloadDeploymentSpec defines the desired state of WorkloadDeployment. _Appears in:_ - [WorkloadDeployment](#workloaddeployment) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `replicas` _integer_ | | 1 | Optional: \{\} | | `template` _[WorkloadReplicaTemplate](#workloadreplicatemplate)_ | | | Required: \{\} | | `deployPolicy` _[WorkloadDeployPolicy](#workloaddeploypolicy)_ | | RollingUpdate | Optional: \{\} | | `artifacts` _[WorkloadDeploymentArtifact](#workloaddeploymentartifact) array_ | | | Optional: \{\} | | `kubernetes` _[KubernetesSpec](#kubernetesspec)_ | Kubernetes groups Kubernetes-specific configuration such as Servicereferences and endpoint management. | | Optional: \{\} | ##### WorkloadList WorkloadList contains a list of Workload. | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `WorkloadList` | | | | `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `items` _[Workload](#workload) array_ | | | | ##### WorkloadReplicaSet WorkloadReplicaSet is the Schema for the artifacts API. _Appears in:_ - [WorkloadReplicaSetList](#workloadreplicasetlist) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `WorkloadReplicaSet` | | | | `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `spec` _[WorkloadReplicaSetSpec](#workloadreplicasetspec)_ | | | | ##### WorkloadReplicaSetList WorkloadReplicaSetList contains a list of WorkloadReplicaSet. | Field | Description | Default | Validation | | --- | --- | --- | --- | | `apiVersion` _string_ | `runtime.wasmcloud.dev/v1alpha1` | | | | `kind` _string_ | `WorkloadReplicaSetList` | | | | `metadata` _[ListMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#listmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | | `items` _[WorkloadReplicaSet](#workloadreplicaset) array_ | | | | ##### WorkloadReplicaSetSpec WorkloadReplicaSetSpec defines the desired state of WorkloadReplicaSet. _Appears in:_ - [WorkloadDeploymentSpec](#workloaddeploymentspec) - [WorkloadReplicaSet](#workloadreplicaset) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `replicas` _integer_ | | 1 | Optional: \{\} | | `template` _[WorkloadReplicaTemplate](#workloadreplicatemplate)_ | | | Required: \{\} | ##### WorkloadReplicaTemplate _Appears in:_ - [WorkloadDeploymentSpec](#workloaddeploymentspec) - [WorkloadReplicaSetSpec](#workloadreplicasetspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `annotations` _object (keys:string, values:string)_ | | | Optional: \{\} | | `labels` _object (keys:string, values:string)_ | | | Optional: \{\} | | `spec` _[WorkloadSpec](#workloadspec)_ | | | Required: \{\} | ##### WorkloadService WorkloadService represents a long-running service that is part of the workload. It is also sometimes referred to as a "sidecar" and is optional. A Service differs from a Component in that it is long-running and represents the Workload's "localhost". Services can bind to TCP & UDP ports, which are accessible by Components within the same workload via "localhost" or "127.0.0.1". Services export a single WIT interface, shaped as wasi:cli/run. Services can import interfaces from any Component within the same workload, or from the Host. _Appears in:_ - [WorkloadSpec](#workloadspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `image` _string_ | | | Required: \{\} | | `imagePullSecret` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#localobjectreference-v1-core)_ | | | Optional: \{\} | | `imagePullPolicy` _[PullPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.31/#pullpolicy-v1-core)_ | | | Optional: \{\} | | `maxRestarts` _integer_ | | | Optional: \{\} | | `localResources` _[LocalResources](#localresources)_ | | | Optional: \{\} | ##### WorkloadSpec WorkloadSpec defines the desired state of Workload. _Appears in:_ - [Workload](#workload) - [WorkloadReplicaTemplate](#workloadreplicatemplate) | Field | Description | Default | Validation | | --- | --- | --- | --- | | `hostSelector` _object (keys:string, values:string)_ | | | Optional: \{\} | | `hostId` _string_ | | | Optional: \{\} | | `environment` _string_ | Environment, if set, scopes scheduling to Hosts whose Environmentmatches this value, regardless of the Workload's own namespace.The value is matched against Host.Environment — typically aKubernetes namespace for in-cluster host pods, or anyoperator-defined identifier for out-of-cluster hosts (e.g. aregion or data center). Only honored when the operator is startedwith allowSharedHosts=true, or when Environment equals theWorkload's namespace. | | Optional: \{\} | | `components` _[WorkloadComponent](#workloadcomponent) array_ | | | Optional: \{\} | | `hostInterfaces` _[HostInterface](#hostinterface) array_ | HostInterfaces declares the host-provided interfaces this workload needs.Two routing invariants are enforced at admission, complementing thehost-side checks: 1. No two entries may be exact duplicates (same namespace, package, name, and version). 2. At most one entry of a given namespace:package may be unnamed — the unnamed entry is the default route and cannot be shared. Declare distinct `name`s to route multiple imports of the same package to different backends. Semver-incompatible versions of the same package may coexist (they are distinct interfaces). | | MaxItems: 64 Optional: \{\} | | `service` _[WorkloadService](#workloadservice)_ | | | Optional: \{\} | | `volumes` _[Volume](#volume) array_ | | | Optional: \{\} | | `kubernetes` _[KubernetesSpec](#kubernetesspec)_ | Kubernetes groups Kubernetes-specific configuration such as Servicereferences and endpoint management. | | Optional: \{\} | --- ## Architecture Cosmonic Control is deployed on a Kubernetes cluster, or standalone with a dedicated API server. All Cosmonic Control deployments consist of the following elements: - [wasmCloud](#wasmcloud) - [Runtime Operator](#runtime-operator) - [Custom Resources](#custom-resources) - [API Server (Kubernetes)](#api-server-kubernetes) - [Cosmonic Operator for wasmCloud](#cosmonic-operator-for-wasmcloud) The diagram below provides a high-level representation of the management flow in a Cosmonic Control deployment. For a more detailed illustration of Cosmonic Control's deployment and tenancy model, see [Multitenancy](/multitenancy/). ![Elements of Cosmonic Control](./img/control-architecture.webp) ### wasmCloud [wasmCloud](https://wasmcloud.com/) is an open source project that orchestrates distributed **WebAssembly (Wasm)** applications across clouds, datacenters, edges, and Kubernetes clusters. Cosmonic Control manages wasmCloud systems, which consist of WebAssembly runtime environments called **hosts**. In Cosmonic Control, wasmCloud hosts are represented by the [Host](/custom-resources/#host) resource and run as Kubernetes pods. Hosts that have the same configuration and serve the same kind of Workloads are grouped together with the Hostgroup annotation. wasmCloud hosts are created in the cluster with a standard Kubernetes deployment specification. WebAssembly components are represented in the [WorkloadDeployment](/custom-resources/#workloaddeployment) specification and run on Host pods. ### Runtime Operator The Runtime Operator manages the custom resources for WebAssembly component deployments, including the [WorkloadDeployment](/custom-resources/#workloaddeployment), the [HTTPTrigger](/custom-resources/#httptrigger) that routes HTTP traffic to it, the [Workload](/custom-resources/#workload), [Artifact](/custom-resources/#artifact), and other custom resources. ### Custom Resources Core primitives for wasmCloud and WebAssembly components are represented by Kubernetes custom resources, which are defined in **Custom Resource Definitions (CRDs)**. Custom resources used in Cosmonic Control include: - [HTTPTrigger](/custom-resources/#httptrigger) - [WorkloadDeployment](/custom-resources/#workloaddeployment) - [Workload](/custom-resources/#workload) - [Host](/custom-resources/#host) Learn more in [Custom Resources](/custom-resources/) and in the API references for [`control.cosmonic.io`](/api-reference/control.cosmonic.io) and [`runtime.wasmcloud.dev`](/api-reference/runtime.wasmcloud.dev). ### API Server (Kubernetes) When Cosmonic Control is deployed on a Kubernetes cluster, the system uses the cluster's API server. When deploying standalone, Cosmonic Control is bundled with a dedicated, independent Kubernetes API server, meaning that you can interact with it exactly the same way as you would on Kubernetes (including using `kubectl`, if you wish), but _without_ the rest of the Kubernetes stack such as etcd, kubelet, etc. ### Cosmonic Operator for wasmCloud [Kubernetes operators](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) are software extensions that use controller patterns and custom resources to extend Kubernetes in a specialized way. The Cosmonic Operator for wasmCloud is an optional way to manage elements of Cosmonic Control, including custom resources and all Kubernetes objects related to wasmCloud (e.g., Services, Secrets, Namespaces). The operator builds on the Cosmonic controller to set up an optimized wasmCloud environment out of the box, with features including: - mTLS certificate management - Policy service - Workload identity - Integration with ArgoCD - OpenTelemetry set up and ready for use - Secure NATS --- ## Custom Resources Core primitives in Cosmonic Control are represented by [**Kubernetes custom resources**](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/). Custom resources may be declaratively provisioned and managed using the API extensions defined in [Custom Resource Definitions (CRDs)](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions). Cosmonic Control uses CRDs for the following custom resources from the `control.cosmonic.io/v1alpha2`, `control.cosmonic.io/v1alpha1` and `runtime.wasmcloud.dev/v1alpha1` API packages: - [Artifact](#artifact) - `runtime.wasmcloud.dev/v1alpha1` - [Host](#host) - `runtime.wasmcloud.dev/v1alpha1` - [HTTPTrigger](#httptrigger) - `control.cosmonic.io/v1alpha2` - [ProjectEnvironment](#projectenvironment) - `control.cosmonic.io/v1alpha1` - [Workload](#workload) - `runtime.wasmcloud.dev/v1alpha1` - [WorkloadDeployment](#workloaddeployment) - `runtime.wasmcloud.dev/v1alpha1` - [WorkloadReplicaSet](#workloadreplicaset) - `runtime.wasmcloud.dev/v1alpha1` For complete API specifications, see the API references for [`control.cosmonic.io`](/api-reference/control.cosmonic.io) and [`runtime.wasmcloud.dev/v1alpha1`](/api-reference/runtime.wasmcloud.dev). ### Artifact The [Artifact](/api-reference/runtime.wasmcloud.dev#artifact) resource represents a WebAssembly component image that can be referenced by workloads. Artifacts define the image location and optional image pull secrets for accessing private registries. The resource can be used to fetch an OCI image and store its contents in a [NATS JetStream Object Store](https://docs.nats.io/nats-concepts/jetstream/obj_store), reducing registry load by downloading artifacts once and distributing them internally. The `Artifact` resource tracks individual revisions, publishing the artifact's location under `Status.ArtifactURL`. [`WorkloadDeployment`](#workloaddeployment) can reference Artifacts as their component image. The controller will automatically roll the deployment when a new image is detected. Example manifest: ```yaml apiVersion: runtime.wasmcloud.dev/v1alpha1 kind: Artifact metadata: name: http-hello-world namespace: default spec: image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0 imagePullSecret: name: ghcr-secret ``` ### Host The [Host](/api-reference/runtime.wasmcloud.dev#host) resource represents a [wasmCloud runtime environment called a host](https://wasmcloud.com/docs/concepts/hosts/). Each host has a unique ID and can run workloads. Example manifest: ```yaml apiVersion: runtime.wasmcloud.dev/v1alpha1 kind: Host metadata: name: host-sample namespace: default labels: hostgroup: default spec: hostId: NABCDEFGHIJKLMNOPQRSTUVWXYZ234567 hostname: host-sample.default httpPort: 4000 ``` ### HTTPTrigger The [`HTTPTrigger`](/api-reference/control.cosmonic.io#httptrigger) resource routes HTTP traffic from Cosmonic Control's ingress to Wasm workloads. Its spec is shaped like the Gateway API [`HTTPRoute`](https://gateway-api.sigs.k8s.io/reference/api-types/httproute/): it lists the hostnames it serves and a set of rules, and each rule sends matching requests to one or more [`WorkloadDeployment`](#workloaddeployment)s in the same namespace, split by weight. The trigger does not create the workload; deploy the `WorkloadDeployment` alongside it. As of Cosmonic Control v0.13.0, `HTTPTrigger` is served as `control.cosmonic.io/v1alpha2`. Earlier releases used `v1alpha1`, which embedded the workload in the trigger; see [Upgrading from v0.12.2 to v0.13.0](/operations/upgrading#httptrigger-v1alpha2-references-a-workloaddeployment) to migrate. See [Ingress and Workloads](/operations/ingress-and-workloads) for a full reference and examples. Example manifest: ```yaml apiVersion: runtime.wasmcloud.dev/v1alpha1 kind: WorkloadDeployment metadata: name: hello-world namespace: default spec: replicas: 1 template: spec: hostInterfaces: - namespace: wasi package: http interfaces: - incoming-handler config: host: hello.localhost.cosmonic.sh components: - name: http image: ghcr.io/cosmonic-labs/control-demos/hello-world:0.1.2 --- apiVersion: control.cosmonic.io/v1alpha2 kind: HTTPTrigger metadata: name: hello-world namespace: default spec: hostnames: - hello.localhost.cosmonic.sh rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: hello-world ``` The `WorkloadDeployment` declares the inbound `wasi:http` interface itself: `incoming-handler` for a WASI P2 component, `handler` for a WASI P3 component. `config.host` names the hostname the workload serves; when it is set, Envoy rewrites the `Host` header to it and keeps the client's original host in `X-Forwarded-Host`. The entry needs no `version`. A `WorkloadDeployment` with no inbound `wasi:http` entry still schedules and reports ready, but it answers every request with `404`. #### Splitting traffic between workloads A rule can list several backends with a `weight` each. Weights are relative, and `0` sends a backend nothing: ```yaml spec: hostnames: - hello.example.com rules: - backendRefs: - name: hello-v1 weight: 90 - name: hello-v2 weight: 10 ``` Backends in one rule that set different `config.host` values cannot share the rule; the trigger reports `IncompatibleBackendHosts`. #### Pinning to a host environment Environment pinning lives on the `WorkloadDeployment`, as `spec.template.spec.environment`. It pins the workloads to hosts reporting that environment in their heartbeat. When unset, the workload is unpinned and the scheduler treats every host as fair game, including shared host groups that serve multiple tenants. Set it to keep workloads on a tenant-scoped host group, or to route them onto a dedicated shared host group such as `infra`: ```yaml apiVersion: runtime.wasmcloud.dev/v1alpha1 kind: WorkloadDeployment spec: template: spec: environment: team-a # ... ``` The `environment` value is matched against the environment HostGroup pods report; see [Installing HostGroups](/operations/deploying-to-cosmonic-control#installing-hostgroups) for setting it on a HostGroup. An `environment` other than the workload's own namespace is honored only while the control chart's `operator.allowSharedHosts` is `true` (the default). #### Autoscaling `WorkloadDeployment` exposes the Kubernetes `/scale` subresource, so a stock `HorizontalPodAutoscaler` or KEDA `ScaledObject` can target it directly. As of v0.13.0 `HTTPTrigger` has no replicas and no `/scale` subresource. See [Autoscaling](/operations/autoscaling). ### ProjectEnvironment :::note[Enterprise] `ProjectEnvironment` is an enterprise feature. It is available with an enterprise license and is not required for free-tier use. ::: The `ProjectEnvironment` resource defines team membership and roles for a Kubernetes namespace managed as a Cosmonic Control project. It is used in conjunction with the enterprise `Project` grouping type to provide namespace-scoped multi-tenancy with RBAC. Example manifest: ```yaml apiVersion: control.cosmonic.io/v1alpha1 kind: ProjectEnvironment metadata: name: my-project namespace: my-namespace spec: groups: - name: platform-team role: admin - name: app-team role: developer ``` ### Workload The [Workload](/api-reference/runtime.wasmcloud.dev#workload) resource represents an application composed of one or more WebAssembly components and optional services. Workloads define the components, their configurations, volume mounts, and host interfaces they consume. Example manifest: ```yaml apiVersion: runtime.wasmcloud.dev/v1alpha1 kind: Workload metadata: name: hello-world namespace: default spec: hostSelector: hostgroup: default components: - name: http-component image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0 poolSize: 10 maxInvocations: 1000 localResources: environment: config: LOG_LEVEL: info allowedHosts: - https://api.example.com hostInterfaces: - namespace: wasi package: http interfaces: - incoming-handler config: address: '0.0.0.0:8080' volumes: - name: cache ephemeral: {} ``` The `poolSize`, `maxConcurrency`, and `maxInvocations` fields on a component set how many calls a warm instance serves at once and how many instances the host keeps. Because instance concurrency drives how many connections a workload needs, size those fields together with the host's per-workload connection quotas. The upstream wasmCloud [Concurrency and connections](https://wasmcloud.com/docs/kubernetes-operator/operator-manual/concurrency-and-connections) guide walks through the interaction and a worked sizing example. ### WorkloadDeployment The [WorkloadDeployment](/api-reference/runtime.wasmcloud.dev#workloaddeployment) resource manages the deployment and scaling of workloads across hosts. It creates and manages WorkloadReplicaSets to ensure the desired number of workload replicas are running. Example manifest: ```yaml apiVersion: runtime.wasmcloud.dev/v1alpha1 kind: WorkloadDeployment metadata: name: hello-world namespace: default spec: replicas: 3 deployPolicy: RollingUpdate artifacts: - name: http-component artifactFrom: name: http-hello-world template: metadata: labels: app: hello-world spec: hostSelector: hostgroup: default components: - name: http-component image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0 poolSize: 10 hostInterfaces: - namespace: wasi package: http interfaces: - incoming-handler config: address: '0.0.0.0:8080' ``` ### WorkloadReplicaSet The [WorkloadReplicaSet](/api-reference/runtime.wasmcloud.dev#workloadreplicaset) resource ensures that a specified number of workload replicas are running at any given time. It is typically managed by a WorkloadDeployment but can be used directly for more granular control. Example manifest: ```yaml apiVersion: runtime.wasmcloud.dev/v1alpha1 kind: WorkloadReplicaSet metadata: name: hello-world-v1 namespace: default spec: replicas: 5 template: metadata: labels: app: hello-world version: v1 spec: hostSelector: hostgroup: default components: - name: http-component image: ghcr.io/wasmcloud/components/http-hello-world-rust:0.1.0 poolSize: 10 hostInterfaces: - namespace: wasi package: http interfaces: - incoming-handler config: address: '0.0.0.0:8080' ``` --- ## Deploy on VMware vSphere Kubernetes Service (VKS) ### Overview This deployment guide demonstrates how to install Cosmonic Control on a [**VMware vSphere Kubernetes Service (VKS)**](https://www.vmware.com/products/cloud-infrastructure/vsphere-kubernetes-service) cluster, enabling the cluster to run both traditional pod deployments and WebAssembly (Wasm) workloads. The deployment process includes the following steps: 1. **Deploy Helm charts** for Cosmonic Control and a HostGroup 2. **Configure Contour ingress** to route subdomains to the **Cosmonic Control Envoy service** for xDS-based Wasm workload routing 3. **Set up Contour ingress** to the **Perses dashboard** for OpenTelemetry (OTEL) querying and monitoring For an overview of the Cosmonic Control architecture, refer to the [Architecture documentation](../architecture.md). ### Requirements To follow this guide, you will need a VKS cluster (**minimum VKS 3.5+ required**), as well as typical VMware Cloud Foundation (VCF) and Kubernetes tooling: * [Helm](https://helm.sh/docs) v3.8.0+ * [`vcf` CLI](https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vcf-9-0-and-later/9-0/building-your-cloud-applications/getting-started-with-the-tools-for-building-applications/installing-and-using-vcf-cli-v9.html) Before installing Cosmonic Control, ensure that [Contour](https://projectcontour.io/) is configured as an ingress controller and [cert-manager](https://cert-manager.io/) is installed (recommended for TLS support). ### Set up VKS Cluster The `vcf` CLI makes it easy to extend your cluster by using the `vcf addons` command. Additional information is available in [the `vcf` CLI documentation](https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vsphere-supervisor-services-and-standalone-components/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/managing-add-ons-in-vks-clusters/view-available-add-ons-using-the-vcf-cli.html). ##### Add VKS standard add-ons ```bash vcf addon repository add vks-repo --url projects.packages.broadcom.com/vsphere/supervisor/packages/2025.10.22/vks-standard-packages:3.5.0-20251022 -n packages ``` ##### Install cert-manager ```bash vcf addon install cert-manager -p cert-manager.kubernetes.vmware.com --version 1.18.2+vmware.2-vks.2 -n packages ``` ##### Install Contour Follow the [VMware VCF documentation](https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vsphere-supervisor-services-and-standalone-components/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/installing-standard-packages-on-tkg-service-clusters/installing-standard-packages-on-tkg-cluster-using-tkr-for-vsphere-8-x/install-contour-with-envoy.html) for detailed Contour installation instructions. Retrieve and customize the default values file: ```bash vcf addon available get contour.kubernetes.vmware.com/1.33.0+vmware.1-vks.1 --default-values-file-output contour-data-values.yaml -n packages ``` A sample configuration is available in the [`vcf-cosmonic-control` GitHub repository](https://github.com/cosmonic-labs/vcf-cosmonic-control/tree/main/vcf-contour/contour-data-values.yaml). Install Contour with the customized values: ```bash vcf addon install contour -p contour.kubernetes.vmware.com --version 1.33.0+vmware.1-vks.1 --values-file contour-data-values.yaml -n packages ``` ### Install Cosmonic Control Refer to the [Cosmonic Control documentation](https://docs.cosmonic.com/install-cosmonic-control#installation) for detailed installation guidelines. #### Option 1: Helm Installation Install Cosmonic Control with a NodePort service for the Envoy proxy. This guide uses Contour as the edge proxy, so disable the chart's built-in Traefik ingress with `ingress.enabled=false`: ```bash helm install cosmonic-control oci://ghcr.io/cosmonic/cosmonic-control \ --version 0.14.0 \ --namespace cosmonic-system \ --create-namespace \ --set ingress.enabled=false \ --set envoy.service.type=NodePort \ --set envoy.service.httpNodePort=30950 ``` ##### Install Cosmonic Control Hostgroup ```bash helm install hostgroup oci://ghcr.io/cosmonic/cosmonic-control-hostgroup \ --version 0.14.0 \ --namespace cosmonic-system ``` #### Configure Contour Ingress The [vcf-contour](https://github.com/cosmonic-labs/vcf-cosmonic-control/tree/main/vcf-contour) folder in the [`vcf-cosmonic-control` GitHub repository](https://github.com/cosmonic-labs/vcf-cosmonic-control) contains ingress configurations for integrating Cosmonic Control with VCF's Contour ingress controller. * **[vcf-contour/vcf-nihao-example-deployment.yaml](https://github.com/cosmonic-labs/vcf-cosmonic-control/tree/main/vcf-contour/vcf-nihao-example-deployment.yaml):** This example demonstrates basic Contour ingress functionality. Refer to the [VMware Contour ingress documentation](https://techdocs.broadcom.com/us/en/vmware-cis/vcf/vsphere-supervisor-services-and-standalone-components/latest/managing-vsphere-kuberenetes-service-clusters-and-workloads/deploying-workloads-on-tkg-service-clusters/ingress-using-contour.html) for implementation details. * **[vcf-contour/cosmonic-ingress.yaml](https://github.com/cosmonic-labs/vcf-cosmonic-control/tree/main/vcf-contour/cosmonic-ingress.yaml):** Configures ingress for the Perses observability dashboard in the `cosmonic-system` namespace. The default hostname is `perses.vcf.cosmonic.world`. For testing, you may use `localhost.cosmonic.sh` which resolves to 127.0.0.1. * **[vcf-contour/vcf-wasm-ingress.yaml](https://github.com/cosmonic-labs/vcf-cosmonic-control/tree/main/vcf-contour/vcf-wasm-ingress.yaml):** Routes all `*.vcf-wasm.cosmonic.world` traffic to the Cosmonic Envoy ingress service. For production environments, consider specifying individual hosts instead of wildcards to reduce invalid hostname requests. * **[vcf-contour/wasm-ingress-examples.yaml](https://github.com/cosmonic-labs/vcf-cosmonic-control/tree/main/vcf-contour/wasm-ingress-examples.yaml):** Contains examples for specific domain routing as well as path-based routing to a Wasm workload. This can be used for when an application is containerized, but has components that are Sandboxed/Wasm workloads as well. #### Option 2: ArgoCD Installation For a GitOps-based deployment approach using ArgoCD, see the [`sample-argo-gh-folder-structure`](https://github.com/cosmonic-labs/vcf-cosmonic-control/tree/main/sample-argo-gh-folder-structure) directory in the [`vcf-cosmonic-control` GitHub repository](https://github.com/cosmonic-labs/vcf-cosmonic-control) for an example repo structure that demonstrates how to deploy both Cosmonic Control and sample Wasm workloads. ### Deploy WebAssembly Workloads An HTTP workload is deployed as a `WorkloadDeployment` that runs the component plus an `HTTPTrigger` that routes requests to it; see [Ingress and Workloads](/operations/ingress-and-workloads#how-to-deploy-webassembly-workloads-with-ingress). Examples are available in the [`sample-argo-gh-folder-structure/apps`](https://github.com/cosmonic-labs/vcf-cosmonic-control/tree/main/sample-argo-gh-folder-structure/apps) folder of the [`vcf-cosmonic-control` GitHub repository](https://github.com/cosmonic-labs/vcf-cosmonic-control). For additional examples and deployment patterns, refer to the [Cosmonic Labs `control-demos` repository](https://github.com/cosmonic-labs/control-demos). #### Example: Using Helm to Deploy The `welcome-tour` demo demonstrates deploying Wasm workloads using Helm. Refer to the [`welcome-tour` repository](https://github.com/cosmonic-labs/control-demos/tree/main/welcome-tour) for implementation details. ```bash helm install welcome-tour --version 0.2.0 oci://ghcr.io/cosmonic-labs/charts/http-trigger \ -f https://raw.githubusercontent.com/cosmonic-labs/control-demos/refs/heads/main/welcome-tour/values.http-trigger.yaml ``` --- ## Sandboxed agent protocol This page is the reference for what a **sandboxed agent** has to implement to run on the **Agents** page of Cosmonic Desktop. To build your own agent step by step, follow [Build a sandboxed agent](/desktop/build-a-sandboxed-agent) first. This page is about sandboxed agents only; native coding agents such as Claude Code are covered in [Connect coding agents](/desktop/coding-agents). ### Summary A sandboxed agent is one **WebAssembly component** (a compiled program that can call only the interfaces it imports) that: 1. Exports `wasi:http/handler@0.3.x` and nothing else. 2. Imports only the interfaces on the [allowlist](#imports-and-exports): no outgoing HTTP, no sockets, no filesystem. 3. Answers `GET /history` with `200` and a JSON body. 4. Answers `POST /task` with `{"task": "..."}` by streaming **NDJSON** lines (one JSON object per line): `delta`, `step`, `done`, and `failed`. It refuses a `POST /task` that lacks the `x-cosmonic-agent-session` header. 5. Writes each message of the conversation, including the person's, to its session journal as an `agent.step.v1` entry. A reopened session is redrawn from that journal, not from the agent. The [minimal agent](https://github.com/cosmonic-labs/awesome-cosmonic/tree/main/components/minimal-agent) is the smallest complete implementation, in about 500 lines of Rust. ### The interfaces An agent reaches the host through the `cosmonic:agent@0.3.0` WIT package. **WIT** (WebAssembly Interface Types) is the language components use to describe what they call, much like a `.d.ts` file describes a JavaScript module. | Interface | What it gives the agent | | ----------------- | ------------------------------------------------------------------------- | | `inference-types` | The types model calls share: messages, chunks, completions, tool calls. | | `models` | `open(alias)`: a model handle for an alias the host resolves. | | `chat` | `chat(model, messages, ...)`: a streamed model call. | | `tools` | `list` and `call`: the tools the session's tool components provide. | | `session` | The session's journal and opaque state, with atomic, conditional commits. | The package is published to `ghcr.io/cosmonic-labs/cosmonic/agent` from the [awesome-cosmonic](https://github.com/cosmonic-labs/awesome-cosmonic/tree/main/wit/cosmonic-agent) repository, where each interface's `.wit` file documents its full contract. To fetch it with `wash wit fetch` or `wkg`, map the `cosmonic` namespace to that registry. The repository's [`wit/wkg-registries.toml`](https://github.com/cosmonic-labs/awesome-cosmonic/blob/main/wit/wkg-registries.toml) does this: ```toml title="wkg-registries.toml" [namespace_registries] wasi = "wasi.dev" [namespace_registries.cosmonic] registry = "ghcr.io" [namespace_registries.cosmonic.metadata] preferredProtocol = "oci" [namespace_registries.cosmonic.metadata.oci] registry = "ghcr.io" namespacePrefix = "cosmonic-labs/" ``` Set `WKG_CONFIG_FILE` to its path for both `wash wit fetch` and `wash build`. The variable replaces your tool's configuration rather than adding to it, which is why the file repeats the `wasi` namespace. ### Imports and exports When you add an agent, Desktop asks the daemon to read the image's WIT (the daemon's `GET /v1/inspect?image=` endpoint, which pulls the image if it isn't stored yet). Desktop refuses to save the agent unless every import matches this list, the same list Desktop's own agents must pass before they are signed: | Package | Interfaces | Versions | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | | `wasi:io` | `error`, `poll`, `streams` | 0.2.x | | `wasi:clocks` | `wall-clock`, `monotonic-clock`, `types` | 0.2.x, 0.3.x | | `wasi:random` | `random`, `insecure-seed` | 0.2.x, 0.3.x | | `wasi:cli` | `environment`, `exit`, `stdin`, `stdout`, `stderr`, `terminal-input`, `terminal-output`, `terminal-stdin`, `terminal-stdout`, `terminal-stderr` | 0.2.x | | `wasi:http` | `types` (types only: it cannot send a request) | 0.3.x | | `cosmonic:agent` | `inference-types`, `chat`, `models`, `tools`, `session` | 0.3.x | The only export allowed is `wasi:http/handler@0.3.x`. Anything else is refused, and the form lists each import or export that is the reason. The usual ones are `wasi:http/client`, `wasi:http/outgoing-handler`, `wasi:sockets`, and `wasi:filesystem`. You can read an image's imports yourself with `wash inspect `. Tool images are not held to this list, because reaching the network is what a tool is for. Desktop still reads them when you save, so a mistyped tool image is caught. A tool may also import `cosmonic:agent/session` to keep [its own durable state](#durable-state-for-tools). #### Importing tools - **An agent that imports `cosmonic:agent/tools` can run with no tools.** When you list none, Desktop declares `tools` for the session and the host serves an empty list: `list` returns no tools and `call` answers `unknown-tool`. So the published `cosmonic:agent/agent` world works as it is. Naming only the interfaces you call in your own world is still a good habit, because it keeps the capabilities an operator reviews down to what your code uses. - **More than one tool needs labelled imports.** When several tool components each export `cosmonic:agent/tools`, the agent must import each one under an `(implements ...)` label naming its component. An unlabelled import cannot tell the tools apart, and the session does not start. ### The workload Each session is one **workload**, the unit of deployment in Cosmonic Desktop, Cosmonic Control, and open source wasmCloud. A workload is a set of components plus the host interfaces they are granted. For an agent you add, Desktop builds it like this: ```yaml apiVersion: runtime.wasmcloud.dev/v1alpha1 kind: Workload metadata: name: agent-session- namespace: default labels: desktop.cosmonic.com/agent-session: desktop.cosmonic.com/agent: custom- spec: components: - name: agent image: poolSize: 1 localResources: allowedHosts: [] # the agent has no network of its own - name: # one per tool you listed image: poolSize: 1 localResources: allowedHosts: [] hostInterfaces: - namespace: wasi package: http interfaces: [handler] config: host: agent-.localhost - namespace: cosmonic package: agent version: 0.3.0 interfaces: [session] config: components: agent tool-components: , # only when the agent has tools - namespace: cosmonic package: agent version: 0.3.0 interfaces: [inference-types, models, chat] config: components: agent backend: model: - namespace: cosmonic # only when the session has no tool component package: agent version: 0.3.0 interfaces: [tools] config: components: agent ``` - **The agent component** gets no outbound hosts. It reaches the model and its session store only through the host. - **Every `cosmonic:agent` entry** is granted to the `agent` component alone (`components: agent`), so a tool cannot read the conversation or spend the model source's budget. - **Each tool** named in `tool-components` gets a session store of its own, separate from the agent's. See [Durable state for tools](#durable-state-for-tools). - **Each tool** reaches only the hosts you listed for it. By default (**Ask first** under **Network**, which appears when you start a session whose tools list a host), Desktop also blocks a tool's first request to each host until you allow it. With **Allow listed hosts**, tools reach their listed hosts without asking; the list still bounds them either way. - **The session stores** are scoped to this workload and deleted with it. Desktop also sets annotations on the workload for the creation time, the manifest format, each tool's hosts, and whether to ask before a tool reaches a new host. None of this makes the agent's own code trustworthy. An agent you add is code you chose to run, and Desktop labels it with your name for it and its image. ### Requests Desktop's main process sends every request through the daemon's local ingress on `127.0.0.1`, with the `Host` header set to the session's host, `agent-.localhost`. The method and path come from a fixed table, so an agent receives only these two requests. #### `GET /history` Desktop's readiness check. After a session starts or restarts, Desktop asks every 1.5 seconds for up to 90 seconds. The session is ready on the first `200` response whose body is JSON other than `null`, `false`, `0`, or `""`. Desktop does not read the body. Desktop's built-in agents answer `{"steps": [...]}` from their journal. #### `POST /task` One turn of the conversation: ```http POST /task HTTP/1.1 Host: agent-3f2b8c1e-9a4d-4e6f-8b2a-1c3d5e7f9a0b.localhost Content-Type: application/json x-cosmonic-agent-session: 1 {"task": "Where is the International Space Station right now?"} ``` - The body is a JSON object with one string field, `task`. - A session can start without a first message. Desktop then sends nothing until the person writes one. - Desktop sends one turn per session at a time. **Refuse a `POST /task` without `x-cosmonic-agent-session`.** Goose, Pi, and the minimal agent answer `403`. The ingress has no other authentication, and a web page can send a cross-origin `POST` without asking first. A custom header forces the browser to ask first (a CORS preflight), which the ingress never answers, so a web page can never deliver one. Apply the same check to anything else your agent does that changes state. The response: - A status other than `200` fails the turn. Desktop shows up to the first 2 KB of the body as the reason. - A `200` body is NDJSON (`application/x-ndjson`): one JSON object per line, each ended by `\n`. Stream the lines as the turn happens. - A failure after the `200` goes in the stream, as a [`failed`](#failed) line. - Closing the stream ends the turn. - When the person selects **Stop**, Desktop drops the connection. Treat it as a cancellation; dropping the model's chunk stream cancels generation at the host. #### Limits | Request | Limit | | -------------- | ----------------------------------------------------- | | `GET /history` | 30 seconds per request; 32 MB per body | | `POST /task` | 10 minutes per turn, wall clock (not an idle timeout) | | `POST /task` | 8 MB per line; 64 MB per turn | ### Response lines Each line is a JSON object with a `type`. Desktop reads four types and ignores any other line, including one that is not JSON. #### `delta` ```json {"type": "delta", "text": "The ISS is over"} ``` Reply text as the model produces it. Desktop shows the deltas as a reply in progress until the next `step` replaces them. #### `step` ```json { "type": "step", "role": "assistant", "text": "", "blocks": ["toolRequest"], "tools": ["iss_position"] } ``` One whole message. The fields Desktop reads: | Field | Type | Meaning | | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- | | `role` | string | `assistant`, `user`, or `tool` for whose message it is; `error` for a failed turn. | | `text` | string | The message text, rendered as untrusted Markdown: no images, no links. | | `blocks` | string[] | The kinds of content in the message: `text`, `toolRequest`, `toolResponse`, `image`, `thinking`, `error`, or your own. | | `tools` | string[] | The tools an assistant message calls, by name. | | `images` | string[] | Images in a tool result, only as `data:image/png`, `jpeg`, `gif`, or `webp` base64 URLs. Anything else is dropped. | | `imagesDropped` | boolean | `true` when you left an image out, so Desktop can say so. | How Desktop draws a step: - `role: "error"`, or `error` in `blocks`: an error in the conversation. - `toolResponse` in `blocks`: a tool's result, folded under the call before it. - `role: "assistant"` with `tools` and no `text`: a tool call. - Any other `assistant` step: a reply. - Anything else: the person's message. Don't stream the person's message back as a step, because Desktop shows it as soon as it is sent. Do write it to the journal. A `role` of `notice` is reserved for Desktop; from an agent it is drawn as an ordinary reply. #### `done` ```json { "type": "done", "input_tokens": 1820, "output_tokens": 96, "model": {"alias": "default", "provider": "cosmonic", "resolved": "gpt-4o-mini"} } ``` The turn finished. Desktop reads `input_tokens`, `output_tokens`, and `model`, and ignores other fields. Goose, Pi, and the minimal agent also send `answer`, `turns`, and `context_messages`, which Desktop does not use. What a reply cost is not taken from this line: Desktop reads that from the host's own record of model calls. #### `failed` ```json {"type": "failed", "error": "the model refused the request: ..."} ``` The turn failed after the `200`. Desktop shows `error` as the reason. Write an `error` step to the journal as well, so the failure is still there when the session is reopened. ### The conversation The conversation on the Agents page is the session's **journal**, an append-only log the host keeps for the session. It is not anything the agent sends to the ingress. When a session is opened, Desktop reads the journal from the daemon (`GET /v1/workloads/{namespace}/{name}/session`), a page at a time, and draws: - every entry of kind `agent.step.v1` whose data is a JSON object with the fields of a [`step`](#step) line (without `type`), timed by when the host accepted it; - every entry of kind `agent.context-reset.v1`, as a note that the agent started a fresh context (write one when your agent cannot read the state it saved, for example after an upgrade changed its format); - nothing else. Entries of your own kinds stay in the journal and are skipped. So an agent writes, through `cosmonic:agent/session`: 1. the person's message, as an `agent.step.v1` entry with `role: "user"`, when the turn starts; 2. each message it streams as a `step`, as the same kind of entry; 3. its working state (the model's context, in any format it likes) as the session's opaque state, committed atomically with the entries that go with it. `session.wit` describes the commit protocol in full: conditional commits, an `agent.op.v1` entry before each model or tool call and an `agent.op-result.v1` entry after it, and what to do on restart with a call whose result was never recorded. The host stamps each entry's author and time; the agent cannot. The session's title also belongs to the host. Desktop sets it from the first message and the person can rename it. #### State across versions The session's state outlives your image. An update, a model switch, or a rollback can hand it to a different version of your agent. Desktop refuses to replace a session while a call it journaled is still open, but it cannot read your state, so compatibility is the agent's job: - **Version your state.** Put a format version in what you save. - **Never overwrite a newer format.** If the state comes from a newer version than yours (after a rollback), start a fresh context with an `agent.context-reset.v1` entry rather than rewriting state you cannot read. - **Migrate older formats** you know, rather than resetting them. - **Carry unresolved calls forward.** A call with an `agent.op.v1` entry and no `agent.op-result.v1` was cut off. Record it as unresolved, never repeat it, and tell the model it did not complete. - **Repair dangling tool calls** before sending the context to a model. Most providers refuse a tool call that has no result. ### Models and tools - **Models.** Open a model with `models.open(alias)`, then call `chat.chat` with the handle. Built-in agents open the alias `default`. The session's binding names the model source and model the person picked, and the host resolves the alias against it, so the agent never names an endpoint, holds a key, or chooses what to spend. - **Tools.** List and call tools with `tools.list` and `tools.call`. The call goes to the tool component through the host; the agent never reaches the tool's hosts itself. When a tool's request is blocked because the person has not allowed that host yet, the tool reports the refusal, and Desktop shows its own prompt from the host's record, not from anything the agent says. ### Durable state for tools A tool component can keep state across restarts by importing `cosmonic:agent/session@0.3.0`, the same interface agents use: a journal, an opaque state, and conditional commits. #### Its own store In an agent session, Desktop grants every tool it runs a store of its own. The host scopes each store to one session and one component, so a tool can never read: - the agent's conversation; - another tool's store; - another session's store. The host resolves every call by the component making it, not by the workload, so sharing a workload with the agent does not give a tool the agent's store. #### The grant The grant is a `tool-components` key on the same `cosmonic:agent` session entry that grants the agent its store: ```yaml - namespace: cosmonic package: agent version: 0.3.0 interfaces: [session] config: components: agent # the agent's store: the conversation tool-components: search,notes # each named tool gets its own store ``` Desktop writes this key for you, naming every tool you list. It matters only if you write a workload yourself: - Both keys belong on one entry, not two, because the runtime folds every entry bound to the same plugin into one configuration. - A component named in both keys is refused when the workload starts, so which store a component reaches is never ambiguous. - A tool that does not import the interface is unaffected. - A component that imports the interface without a grant does not start. #### Encryption and deletion Each session's data is sealed with that session's own key, and deleting the session deletes the key with its stores. The master key that protects the session keys lives: | Platform | Where the master key lives | | -------- | ----------------------------------------------------------------------------------------------------------- | | macOS | The login Keychain. | | Windows | A file protected by Windows DPAPI for your user, in a local (not roaming) folder. | | Linux | The Secret Service (GNOME Keyring or KWallet). On a machine without one, a file readable only by your user. | #### Store limits | What | Limit | | -------------------------------- | ------ | | One saved state or journal entry | 8 MiB | | One store | 64 MiB | A write past either limit is refused with the `too-large` error, which names the limit, and nothing from that commit is written. #### Using it - **On start**, call `current` to read back the state and the version you last saved. - **To save**, call `commit` with the new state, conditional on the version `current` returned. If something else wrote in between, the commit comes back stale instead of overwriting it. - **Keep the state small.** Save what the tool needs to resume, not a cache of everything it has seen. The comments in [`session.wit`](https://github.com/cosmonic-labs/awesome-cosmonic/blob/main/wit/cosmonic-agent/session.wit) describe every call and error. ### Logs What the agent writes to stdout and stderr is the workload's log. When an agent does not start, Desktop offers **Open logs** on the session. ### Adding an agent in Desktop You can add an agent in two places, with the same form: - **Add a new agent…** in the **Agent** dropdown on the **Start a session** screen of the Agents page; - **Add an agent** in the **Your agents** card under **Settings → Sandboxed agents**, which is also where you edit or remove agents. | Field | What to enter | | ---------------------- | ------------------------------------------------------------------------------ | | **Name** | How the agent appears in the **Agent** list. Up to 64 characters, unique. | | **Image** | The agent component's OCI reference. Desktop checks its imports before saving. | Desktop's built-in registry is a convenient place to push an image from your machine: ```shell wash oci push --insecure oci.localhost:8200/apps/my-agent:0.1.0 my_agent.wasm ``` Push each rebuild under a new tag; Desktop pins an image to the build its tag first pointed to. Changing an agent's image affects new sessions only. Sessions you already started keep the images they started with, and Desktop stops sending them messages. Removing an agent leaves its sessions in place, unmessaged, until you add it again. #### Sessions Desktop doesn't recognize A session started outside the Agents page (for example from a manifest applied with the CLI or over MCP), or whose images no longer match a saved agent, shows **Desktop doesn't recognize this agent**. Select **Add this agent** to save it as one of your agents. Desktop checks its imports first, may restart the session in the shape described in [The workload Desktop runs](#the-workload-desktop-runs), and keeps the conversation. ### Checking your agent 1. Build the component and push it to a registry Desktop can pull from. 2. Add it with **Add a new agent…**. **Check and save** runs the import check. 3. Start a session. If it never gets past starting, it is not answering `GET /history` with `200` and JSON within 90 seconds: select **Open logs**. 4. Send a message, then select another session and come back. If the conversation is gone, the agent is streaming steps without writing them to its journal. --- ## Google Antigravity Sandbox Cosmonic Desktop is a local **AI sandbox** for [Google Antigravity](https://antigravity.google/): the code Antigravity writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching Antigravity from Build with the sandbox playbook and one for installing Desktop's MCP server into Antigravity; use either or both. ### Sandbox Google Antigravity with Cosmonic Desktop #### 1. Settings → Agents: launch Antigravity from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.gemini/antigravity-cli` (Antigravity shares the `~/.gemini` home with Gemini CLI; its own state under `antigravity-cli` is how Desktop tells the two apart)) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **Google Antigravity** to install the `cosmonic-sandbox` skill at `~/.gemini/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches Antigravity to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch Antigravity directly: open **Build** in the sidebar, pick Antigravity as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. Antigravity reads the same `~/.gemini/skills` directory as Gemini CLI, so installing the skill for one covers both. #### 2. Settings → MCP Server: install Desktop's MCP server into Antigravity {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **Google Antigravity**: The Antigravity CLI (`agy`) has no `mcp` subcommand, so Desktop's toggle edits the MCP config the CLI and the Antigravity IDE share, `~/.gemini/config/mcp_config.json`, directly. This registers `cosmonicd mcp serve` as an MCP server in Antigravity's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. To do it by hand, add the server to `~/.gemini/config/mcp_config.json`: ```json { "mcpServers": { "cosmonic": { "command": "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd", "args": ["mcp", "serve"] } } } ``` #### 3. Verify the connection {#verify} Open the MCP panel in the Antigravity IDE (or start `agy`); the `cosmonic` server should be listed. Antigravity reads only this home-level file, not the inline `mcpServers` map in `settings.json` that Gemini CLI uses and not project-local configs. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch Antigravity. ### What Antigravity can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## Claude Code Sandbox Cosmonic Desktop is a local **AI sandbox** for [Claude Code](https://claude.com/claude-code): the code Claude Code writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching Claude Code from Build with the sandbox playbook and one for installing Desktop's MCP server into Claude Code; use either or both. ### Sandbox Claude Code with Cosmonic Desktop #### 1. Settings → Agents: launch Claude Code from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.claude`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **Claude Code** to install the `cosmonic-sandbox` skill at `~/.claude/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches Claude Code to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch Claude Code directly: open **Build** in the sidebar, pick Claude Code as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. For Claude Code the skill is the sandbox instructions on their own; the full Cosmonic plugin—slash commands, MCP wiring, and the sandbox gate—installs separately with `claude plugin install cosmonic@cosmonic`. #### 2. Settings → MCP Server: install Desktop's MCP server into Claude Code {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **Claude Code**: Desktop runs `claude mcp add` for you. This registers `cosmonicd mcp serve` as an MCP server in Claude Code's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. The manual equivalent (using [the daemon binary's path](/desktop/troubleshooting#where-everything-lives) on your platform—macOS shown): ```shell claude mcp add --scope user cosmonic -- "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd" mcp serve ``` #### 3. Verify the connection {#verify} ```shell claude mcp get cosmonic # shows the server and its connection state ``` Run `claude mcp get cosmonic`; it should list the server as connected while Desktop is running. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch Claude Code. ### What Claude Code can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## Claude Desktop Sandbox Cosmonic Desktop is a local **AI sandbox** for [Claude Desktop](https://claude.com/download): the code Claude writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Claude Desktop is an MCP host rather than a coding agent, so there is one setting to wire up: Desktop's MCP server from Settings → MCP Server. Claude Desktop then gets the tools, and the sandbox skills reach it over MCP. ### Sandbox Claude Desktop with Cosmonic Desktop #### 1. Settings → Agents: Claude Desktop is detected, nothing to install {#settings-agents} Open **Settings → Agents**. Claude Desktop appears in **Detected agents** when its app config directory exists, marked MCP-only: it is not launchable from Build and there is no skill to install, because Claude Desktop has no skills directory. The playbooks reach it over MCP instead (see Skills over MCP below). #### 2. Settings → MCP Server: install Desktop's MCP server into Claude Desktop {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **Claude Desktop**: Claude Desktop has no MCP CLI and its config sits outside the home dot-dir convention, so Desktop's toggle edits `claude_desktop_config.json` directly (`~/Library/Application Support/Claude/` on macOS, `%APPDATA%\Claude\` on Windows, `~/.config/Claude/` on Linux). Restart Claude Desktop afterwards. This registers `cosmonicd mcp serve` as an MCP server in Claude Desktop's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. To do it by hand, add the server to `claude_desktop_config.json` and restart Claude Desktop: ```json { "mcpServers": { "cosmonic": { "command": "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd", "args": ["mcp", "serve"] } } } ``` #### 3. Verify the connection {#verify} After restarting, the `cosmonic` server shows in Claude Desktop's tools menu; ask Claude to list Cosmonic workloads and the `cosmonic_*` tools should answer while Desktop is running. Claude Desktop speaks stdio only, so sandboxed MCP-server workloads you publish from Desktop register through the bundled `cosmonic-stdio` shim; see [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on for the agents it launches. ### What Claude Desktop can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## OpenAI Codex Sandbox Cosmonic Desktop is a local **AI sandbox** for [OpenAI Codex](https://developers.openai.com/codex/): the code Codex writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching Codex from Build with the sandbox playbook and one for installing Desktop's MCP server into Codex; use either or both. ### Sandbox OpenAI Codex with Cosmonic Desktop #### 1. Settings → Agents: launch Codex from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.codex`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **OpenAI Codex** to install the `cosmonic-sandbox` skill at `~/.agents/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches Codex to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch Codex directly: open **Build** in the sidebar, pick Codex as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. Codex reads the shared `~/.agents/skills` directory (the cross-agent AgentSkills convention) rather than a Codex-specific path, so the skill Desktop installs there is visible to any other agent that follows the same convention. #### 2. Settings → MCP Server: install Desktop's MCP server into Codex {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **OpenAI Codex**: Desktop runs `codex mcp add` for you. This registers `cosmonicd mcp serve` as an MCP server in Codex's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. The manual equivalent (using [the daemon binary's path](/desktop/troubleshooting#where-everything-lives) on your platform—macOS shown): ```shell codex mcp add cosmonic -- "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd" mcp serve ``` Or add it to `~/.codex/config.toml` directly: ```toml [mcp_servers.cosmonic] command = "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd" args = ["mcp", "serve"] ``` #### 3. Verify the connection {#verify} ```shell codex mcp list # the cosmonic server should be listed ``` Run `codex mcp list`; `cosmonic` should appear. The tools work only while Desktop is running. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch Codex. ### What Codex can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## Gemini CLI Sandbox Cosmonic Desktop is a local **AI sandbox** for [Gemini CLI](https://github.com/google-gemini/gemini-cli): the code Gemini CLI writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching Gemini CLI from Build with the sandbox playbook and one for installing Desktop's MCP server into Gemini CLI; use either or both. ### Sandbox Gemini CLI with Cosmonic Desktop #### 1. Settings → Agents: launch Gemini CLI from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.gemini`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **Gemini CLI** to install the `cosmonic-sandbox` skill at `~/.gemini/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches Gemini CLI to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch Gemini CLI directly: open **Build** in the sidebar, pick Gemini CLI as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. Gemini CLI and Google Antigravity share the `~/.gemini/skills` directory, so installing the skill for one covers both. #### 2. Settings → MCP Server: install Desktop's MCP server into Gemini CLI {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **Gemini CLI**: Desktop runs `gemini mcp add` for you. This registers `cosmonicd mcp serve` as an MCP server in Gemini CLI's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. The manual equivalent (using [the daemon binary's path](/desktop/troubleshooting#where-everything-lives) on your platform—macOS shown): ```shell gemini mcp add cosmonic "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd" mcp serve ``` #### 3. Verify the connection {#verify} ```shell gemini mcp list # the cosmonic server should be listed and enabled ``` Run `gemini mcp list`. If the server shows as _Disabled (folder untrusted)_, trust the workspace (run `gemini` in the folder and accept the prompt), or register it machine-wide in `~/.gemini/settings.json` instead. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch Gemini CLI. ### What Gemini CLI can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## Hermes Agent Sandbox Cosmonic Desktop is a local **AI sandbox** for **Hermes Agent**: the code Hermes writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching Hermes from Build with the sandbox playbook and one for installing Desktop's MCP server into Hermes; use either or both. ### Sandbox Hermes Agent with Cosmonic Desktop #### 1. Settings → Agents: launch Hermes from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.hermes`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **Hermes Agent** to install the `cosmonic-sandbox` skill at `~/.hermes/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches Hermes to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch Hermes directly: open **Build** in the sidebar, pick Hermes as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. #### 2. Settings → MCP Server: install Desktop's MCP server into Hermes {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **Hermes Agent**: Hermes' own CLI is interactive, so Desktop's toggle edits `~/.hermes/config.yaml` directly. This registers `cosmonicd mcp serve` as an MCP server in Hermes's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. The equivalent manual command: ```shell hermes mcp add cosmonic --command "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd" --args mcp serve ``` #### 3. Verify the connection {#verify} Open `~/.hermes/config.yaml`; the `cosmonic` server entry should be present. Start Hermes and ask it to list Cosmonic workloads to confirm the tools are live. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch Hermes. ### What Hermes can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## Kiro CLI Sandbox Cosmonic Desktop is a local **AI sandbox** for [Kiro CLI](https://kiro.dev/): the code Kiro writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching Kiro from Build with the sandbox playbook and one for installing Desktop's MCP server into Kiro; use either or both. ### Sandbox Kiro CLI with Cosmonic Desktop #### 1. Settings → Agents: launch Kiro from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.kiro`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **Kiro CLI** to install the `cosmonic-sandbox` skill at `~/.kiro/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches Kiro to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch Kiro directly: open **Build** in the sidebar, pick Kiro as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. Kiro reads the open Agent Skills standard from `~/.kiro/skills`, so the skill Desktop installs there is picked up natively the next time Kiro starts. #### 2. Settings → MCP Server: install Desktop's MCP server into Kiro {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **Kiro CLI**: Desktop's toggle writes the standard `mcpServers` entry into `~/.kiro/settings/mcp.json`, the user-level file Kiro reads (the legacy `~/.aws/amazonq/mcp.json` is migrated there when Kiro installs). This registers `cosmonicd mcp serve` as an MCP server in Kiro's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. To do it by hand, add the server to `~/.kiro/settings/mcp.json`: ```json { "mcpServers": { "cosmonic": { "command": "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd", "args": ["mcp", "serve"] } } } ``` #### 3. Verify the connection {#verify} ```shell kiro-cli chat # then ask Kiro to list Cosmonic workloads; the cosmonic_* tools should answer ``` Start `kiro-cli chat` and ask it to list Cosmonic workloads; the `cosmonic_*` tools answer while Desktop is running. Build launches Kiro with `--trust-tools=read` by default (a guarded mode), with "Ask every time" and "Trust all tools" as alternatives. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch Kiro. ### What Kiro can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## LM Studio Sandbox Cosmonic Desktop is a local **AI sandbox** for [LM Studio](https://lmstudio.ai/): the code the model writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. LM Studio is a GUI MCP host rather than a terminal coding agent, so the wiring is two settings and one selection inside LM Studio: a config preset from Settings → Agents, Desktop's MCP server from Settings → MCP Server, and the preset picked in a chat. ### Sandbox LM Studio with Cosmonic Desktop #### 1. Settings → Agents: install the Cosmonic Sandbox preset {#settings-agents} Open **Settings → Agents**. LM Studio appears in **Detected agents** when `~/.lmstudio` exists, marked as a GUI host: it is not launchable from Build. In the **Skills** card, turn on **LM Studio** to install a **config preset**, not a skill, at `~/.lmstudio/cosmonic-sandbox/`. The preset is a condensed system prompt that teaches the model the sandbox loop; select **Cosmonic Sandbox** as the preset in an LM Studio chat to use it. #### 2. Settings → MCP Server: install Desktop's MCP server into LM Studio {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **LM Studio**: Desktop's toggle merges the entry into `~/.lmstudio/mcp.json` (LM Studio 0.3.17 or later reads the standard `mcpServers` schema there). This registers `cosmonicd mcp serve` as an MCP server in LM Studio's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. Or paste this through LM Studio's in-app editor (**Program → Install → Edit mcp.json**): ```json { "mcpServers": { "cosmonic": { "command": "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd", "args": ["mcp", "serve"] } } } ``` #### 3. Verify the connection {#verify} In an LM Studio chat, enable the `cosmonic` MCP server in the tools panel and select the **Cosmonic Sandbox** preset; ask the model to list Cosmonic workloads and the `cosmonic_*` tools should answer while Desktop is running. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on for the agents it launches. ### What LM Studio can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## OpenClaw Sandbox Cosmonic Desktop is a local **AI sandbox** for **OpenClaw**: the code OpenClaw writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching OpenClaw from Build with the sandbox playbook and one for installing Desktop's MCP server into OpenClaw; use either or both. ### Sandbox OpenClaw with Cosmonic Desktop #### 1. Settings → Agents: launch OpenClaw from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.openclaw`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **OpenClaw** to install the `cosmonic-sandbox` skill at `~/.openclaw/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches OpenClaw to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch OpenClaw directly: open **Build** in the sidebar, pick OpenClaw as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. #### 2. Settings → MCP Server: install Desktop's MCP server into OpenClaw {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **OpenClaw**: OpenClaw manages MCP servers natively (`mcp.servers` in `~/.openclaw/openclaw.json`); Desktop's toggle runs `openclaw mcp add` for you. This registers `cosmonicd mcp serve` as an MCP server in OpenClaw's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. The manual equivalent (using [the daemon binary's path](/desktop/troubleshooting#where-everything-lives) on your platform—macOS shown): ```shell openclaw mcp add cosmonic --command "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd" --arg mcp --arg serve ``` #### 3. Verify the connection {#verify} ```shell openclaw mcp list # the cosmonic server should be listed ``` Run interactively, `openclaw mcp add` probes the server before saving, so a successful add is itself the check; `openclaw mcp list` shows it afterwards. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch OpenClaw. ### What OpenClaw can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## OpenCode Sandbox Cosmonic Desktop is a local **AI sandbox** for [OpenCode](https://opencode.ai/): the code OpenCode writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching OpenCode from Build with the sandbox playbook and one for installing Desktop's MCP server into OpenCode; use either or both. ### Sandbox OpenCode with Cosmonic Desktop #### 1. Settings → Agents: launch OpenCode from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.config/opencode` or `~/.opencode`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **OpenCode** to install the `cosmonic-sandbox` skill at `~/.config/opencode/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches OpenCode to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch OpenCode directly: open **Build** in the sidebar, pick OpenCode as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. Alongside the skill, Desktop installs a **Cosmonic agent profile** at `~/.config/opencode/agents/cosmonic.md`. That profile is OpenCode's guarded mode: it keeps the sandbox in the loop without per-action prompts, and it is what Build selects with `--agent cosmonic`. #### 2. Settings → MCP Server: install Desktop's MCP server into OpenCode {#settings-mcp-server} Open **Settings → MCP Server**. Under **Install in your AI client**, turn on **OpenCode**: OpenCode's server map is its own shape (a top-level `mcp` map, not `mcpServers`) and `opencode mcp add` cannot register a local stdio server without prompting, so Desktop's toggle edits `~/.config/opencode/opencode.json` (or `.jsonc`) directly. This registers `cosmonicd mcp serve` as an MCP server in OpenCode's own configuration, and the `cosmonic_*` tools (observe, deploy, build) are available the next time you start it. Expand **Instructions** on the same card to copy the manual snippet instead. To do it by hand, add this to `~/.config/opencode/opencode.json`: ```json { "mcp": { "cosmonic": { "type": "local", "command": [ "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd", "mcp", "serve" ], "enabled": true } } } ``` #### 3. Verify the connection {#verify} ```shell opencode mcp list # the cosmonic server should be listed and enabled ``` Run `opencode mcp list`; `cosmonic` should be listed and enabled. Build starts OpenCode with `--agent cosmonic` by default, the guarded profile Desktop installs, because OpenCode ships only a full-bypass flag and no middle mode of its own. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch OpenCode. ### What OpenCode can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## NVIDIA OpenShell Sandbox Cosmonic Desktop is a local **AI sandbox** for **NVIDIA OpenShell**: the code OpenShell writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching OpenShell from Build with the sandbox playbook and one for installing Desktop's MCP server into OpenShell; use either or both. ### Sandbox NVIDIA OpenShell with Cosmonic Desktop #### 1. Settings → Agents: launch OpenShell from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.openshell`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **NVIDIA OpenShell** to install the sandbox policy template at `~/.openshell/cosmonic/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The policy registers Desktop's MCP server inside each OpenShell sandbox at creation time. Once it is installed, Build can launch OpenShell directly: open **Build** in the sidebar, pick OpenShell as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. #### 2. Settings → MCP Server: install Desktop's MCP server into OpenShell {#settings-mcp-server} Open **Settings → MCP Server** for the server's status and the manual snippet. OpenShell registers MCP servers per sandbox, through a policy applied at sandbox-create time, so there is no persistent client config for Desktop to edit and OpenShell is not in the **Install in your AI client** list. Instead, **Settings → Agents** installs a sandbox _policy template_ rendered with this machine's daemon paths, and Build applies it automatically (via `OPENSHELL_SANDBOX_POLICY`) when it launches OpenShell. To apply the policy by hand when creating a sandbox: ```shell openshell sandbox create --policy ~/.openshell/cosmonic/policies/cosmonic-desktop-mcp.yaml ``` #### 3. Verify the connection {#verify} Inside a sandbox created with the policy, the `cosmonic_*` tools are available to the agent. Review the bundled README under `~/.openshell/cosmonic/` before applying the policy. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch OpenShell. ### What OpenShell can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## Pi Sandbox Cosmonic Desktop is a local **AI sandbox** for [Pi](https://pi.dev/): the code Pi writes is compiled to WebAssembly and runs on your own machine with no access to your files, network, or keys until you grant it. Two settings wire it up, one for launching Pi from Build with the sandbox playbook and one for installing Desktop's MCP server into Pi; use either or both. ### Sandbox Pi with Cosmonic Desktop #### 1. Settings → Agents: launch Pi from Build {#settings-agents} Open **Settings → Agents**. The **Detected agents** card lists every coding agent Desktop found by its home directory (`~/.pi`) or on your login shell's PATH, and says whether each one is launchable from Build. In the **Skills** card, turn on **Pi** to install the `cosmonic-sandbox` skill at `~/.pi/agent/skills/cosmonic-sandbox/`. This is the same install the one-time **Set up your coding agents** dialog offers on first run; nothing is installed silently. The skill is the sandbox playbook: it teaches Pi to compile what it generates to a WebAssembly component and deploy it into the local sandbox instead of running it raw on your host. Once it is installed, Build can launch Pi directly: open **Build** in the sidebar, pick Pi as the agent, describe what you want, and its output lands in a sandboxed workload you can inspect before it runs. Desktop installs the skill into Pi's own agent directory rather than the shared `~/.agents/skills` alias, because Pi's own directory takes precedence and this is what makes it load the Pi build of the skill. #### 2. Settings → MCP Server: install Desktop's MCP server into Pi {#settings-mcp-server} Pi ships no MCP client of its own, so there are two parts. First install the **pi-mcp-adapter** package once; it adds an MCP client to Pi and reads the standard `mcpServers` schema from `~/.pi/agent/mcp.json`: ```shell pi install npm:pi-mcp-adapter ``` Then open **Settings → MCP Server** and turn on **Pi** under **Install in your AI client**: Desktop writes the entry to `~/.pi/agent/mcp.json` (Pi's own file, never the shared roots the adapter also reads). The manual equivalent (using [the daemon binary's path](/desktop/troubleshooting#where-everything-lives) on your platform—macOS shown): ```json { "mcpServers": { "cosmonic": { "command": "/Applications/Cosmonic Desktop.app/Contents/Resources/cosmonicd", "args": ["mcp", "serve"] } } } ``` #### 3. Verify the connection {#verify} Start `pi` and ask it to list Cosmonic workloads; with the adapter installed the `cosmonic_*` tools answer while Desktop is running. Build launches Pi in "Sandbox only" mode by default (`--exclude-tools bash`), so the agent can build and deploy into the sandbox but cannot run shell commands on your host. #### Skills over MCP Desktop's MCP server also publishes its skill family—`cosmonic-sandbox`, `cosmonic-go`, `cosmonic-nats`, `cosmonic-nats-tuning`, and `cosmonic-kafka`—through the MCP **Skills extension** (`io.modelcontextprotocol/skills`). A client that supports [Skills over MCP](https://github.com/modelcontextprotocol/ext-skills) gets the playbooks the moment it connects, with nothing written to its skills directory, so the MCP registration above is enough on its own for those clients. The on-disk skill from **Settings → Agents** covers clients that read a skills directory but do not yet speak the extension, and it is what Build relies on to launch Pi. ### What Pi can do once connected Its full tool surface (observe, deploy, build) and the guardrails are covered in [Connect Coding Agents](/desktop/coding-agents#what-the-agent-can-do); the tools work only while Cosmonic Desktop is running. ### Frequently asked questions ### Next steps - The server's trust model and tool reference: [Connect Coding Agents](/desktop/coding-agents). - Sandbox the tool servers your agent calls: [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). - What an AI sandbox is and why it matters: [AI Sandbox](https://cosmonic.com/ai-sandbox/). --- ## Application Views import Screenshot from '@site/src/components/Screenshot'; This page tours every view in Cosmonic Desktop. Desktop opens to **Workloads**. The sidebar organizes the views into three groups: - **Explore**: **Academy**, **Launchpad**, **Build**, and **Inspect**. - **Fleet**: **Workloads** and everything they use: **Capabilities**, **Logs**, and **Secrets**. - **Inventory**: **Local Registry**. Right-click any sidebar item and choose **Set as default screen** to open Desktop on that view instead. The **header** carries a terminal toggle (it shows or hides the terminal dock at the bottom of the window), **Help**, and **Settings**; once you have more than one local host, a **host-tabs** row appears below it. The **status bar** (bottom) indicates the host state (**running**/starting/stopped), connection status (**live**, **stale**, or **disconnected**—see [Troubleshooting](/desktop/troubleshooting#the-status-bar-says-stale-or-disconnected)), daemon resource usage, a theme toggle, and the daemon version. ### Workloads The view Desktop opens to, and the organizational unit of everything else: each workload bundles the components it runs and the services they consume, deployed onto a host. The table shows each workload's capabilities, invocation rate, and status; the row tools inspect, edit, or delete it, and expanding a row reveals the components inside—with versions, pinned digests, per-component ingress links, and deployment history. **Start workload** (top right) opens the [run flow](/desktop/quickstart#2-run-a-component). ### Academy Cosmonic's learning hub provides a self-paced set of **missions** to get you acquainted with Cosmonic Desktop and the WebAssembly ecosystem. (Content is served by the app itself and is available offline.) ### Launchpad A curated catalog of templates, applications, and samples you can run on your local host with one click. Browse or search entries by name and tag; each card shows its capabilities, tags, and source, and **Launch** takes the entry through the same review-and-apply flow as any workload you deploy by hand—[digest-pinned, signature-checked, and sandboxed with deny-by-default egress](/desktop/#secure-by-default). Entries from Cosmonic's own source are marked as official. ### Build Desktop's prompt-to-production launcher. Describe the app you want, and Build creates a **new project repo** from a starter template in your projects directory. The template picker is on **Auto** by default, which picks an HTTP, MCP server, NATS, or Kafka starter from your prompt; choose one yourself to override it. Then Build launches your **native coding agent** on the project: pick any detected agent ([Claude Code, OpenAI Codex, Gemini CLI, Google Antigravity, Hermes, OpenClaw, NVIDIA OpenShell, and more](/desktop/coding-agents), each shown when its [cosmonic-sandbox skill](/desktop/coding-agents#agent-skills) is installed and its binary is on your `PATH`). The agent runs in the **Integrated terminal** dock under the page by default, or in a terminal app you choose from the **Terminal** menu. The coding agent runs natively, with your user permissions, outside the sandbox; Build says so under the pickers. Each agent offers its own model and start-mode choices, from plan-first through guarded modes (where the agent's own best-effort guardrail, such as a classifier, reviews actions) to fully unattended, and the `$` line at the bottom of the card shows exactly what will run before you select **Launch**. From there the agent drives Desktop over [MCP](/desktop/coding-agents#what-the-agent-can-do): it builds, deploys, and iterates locally, and the result appears in your **Workloads** view, sandboxed like anything you deploy by hand. The **Previous sessions** list to the right shows the coding-agent sessions already on this machine, so you can open one read-only or resume it. ### Capabilities Every [capability](/platform-concepts/capabilities) in use across your workloads. Expand a row to see the importing components and jump to inspection. ### Logs Structured logs from the active host: the daemon, its workloads, and the components they run. Sort or filter any column, or turn on live tail. This is [the first place to look](/desktop/troubleshooting#start-with-the-logs-view) when something fails: the reconciler fails fast, so the first `Failed` event for a workload usually carries the real cause. ### Secrets & config Secret **references** and reusable named configs. References resolve from your OS keychain or the environment at workload start; values never appear in YAML, the API, or this UI. Named configs are key/value maps you can attach to multiple workloads. ### Local Registry The daemon's content-addressed OCI cache, browsed as a registry → repository → version tree. Each version shows its digest, size, cosign signature status, and whether it's in use by a running workload, with tools to inspect, jump to where it's running, pull, prune, and import or export air-gap bundles (see [the daemon's `oci` commands](/desktop/daemon#the-cosmonicd-cli)). ### Inspect A component's imports and exports, graphed. Pick any local component, service, or workload and see its declared interfaces as a color-coded connection diagram, with a structured summary, security and provenance details, and hyperlinked WIT. It's the same information the [run flow's review step](/desktop/quickstart#3-review-and-apply) summarizes before you deploy. ### Settings App and daemon configuration, grouped into five sections: - **Setup & trust**: **General** (appearance, host port), **Privacy** ([the two telemetry consents](/desktop/privacy-and-telemetry)), and **Security** (signature policy, advisory by default, with strict signing available globally or per registry). - **Learn & launch**: the content sources for **Academy** and **Launchpad**. - **Runtime**: **Project folders**, **Toolchain** (the [build-toolchain doctor](/desktop/build-toolchain/)), **Built-in plugins** (host capabilities, including [NATS messaging clusters](/desktop/messaging-with-nats/)), **Registries** (credentials and mirrors), **Host Labels** (labels a workload can target for placement), **Admin Workloads** (a read-only view of the platform's `cosmonic-system` workloads), and **Notifications** (per-component permission for the `cosmonic:notify` host capability). - **Agent workspace**: **Agents** (install and update the [cosmonic-sandbox skill](/desktop/coding-agents#agent-skills) for each detected coding agent) and **MCP Server** ([agent access](/desktop/coding-agents)). - **Maintenance**: **Logging** and **Updates**. ### The menu-bar tray The tray icon is a lightweight native companion (not the app itself) that reflects daemon state at a glance and handles the essentials without opening the app: **Open Control Panel** (launches the full app), **Start/Stop daemon**, **Start on login**, and **Uninstall Cosmonic**. Closing the app window doesn't stop your workloads. The daemon and tray keep running; quitting the tray leaves the daemon service itself untouched. [The Cosmonic Tray](/desktop/tray) covers the tray in full. :::note[No tray icon on GNOME?] GNOME needs the AppIndicator extension to show tray icons—see [Troubleshooting](/desktop/troubleshooting#no-tray-icon-on-gnome). ::: --- ## Build a sandboxed agent In this tutorial you build a small AI agent, run it on the **Agents** page of Cosmonic Desktop, and hold a conversation with it that survives closing and reopening the session. The agent you build runs in a sandbox. It has no network access, no filesystem, and no API key of its own. When it needs a model, it asks Desktop, and Desktop makes the call with a key the agent never sees. :::info[Two kinds of agent] Desktop works with two different kinds of agent, and this page is about only one of them. - **Sandboxed agents** run inside Desktop on the **Agents** page. The agent itself is the sandboxed code. This tutorial builds one. - **Native coding agents** such as Claude Code or Codex run on your machine with your user's permissions. Desktop sandboxes the code they build, not the agents themselves. See [Connect coding agents](/desktop/coding-agents) for those. ::: ### What you'll build You'll start from the **minimal agent**, a complete example in the public [awesome-cosmonic](https://github.com/cosmonic-labs/awesome-cosmonic) repository, about 500 lines of Rust. You'll give it a personality of your own, build it, and add it to Desktop. By the end you'll have: - an agent image in Desktop's built-in registry; - that agent listed in the **Agent** dropdown next to Desktop's own agents; - a session where the agent streams its replies and remembers what you told it. ### What you'll need - **Cosmonic Desktop**, running, with **Agents** in the sidebar under **AI & agents**. - **`wash` 2.8.0 or later**, the wasmCloud command line tool that builds and pushes components. Install it from the [wasmCloud installation guide](https://wasmcloud.com/docs/installation) and check it with `wash --version`. - **Rust 1.94 or later**, plus the WebAssembly compile target. Add the target with: ```shell rustup target add wasm32-wasip2 ``` - **git**, to fetch the example. - **A model for the agent to talk to.** Either [Ollama](https://ollama.com) running on your machine with a model pulled, or an API key for Anthropic, OpenAI, or an OpenAI-compatible service. You'll pick it when you start the session. ### 1. Get the example Clone the repository and move into the example's directory: ```shell git clone https://github.com/cosmonic-labs/awesome-cosmonic.git cd awesome-cosmonic/components/minimal-agent ``` The files you'll touch are: | File | What it is | | ------------------------------- | --------------------------------------------------------------------- | | `src/lib.rs` | The agent: it answers HTTP requests from Desktop and calls the model. | | `wit/world.wit` | The list of host interfaces the agent is allowed to use. | | `.wash/config.yaml` | Tells `wash` how to build the project and where the output lands. | | `../../wit/wkg-registries.toml` | Tells `wash` which registry publishes the `cosmonic` interfaces. | ### 2. Fetch the agent interfaces Your agent talks to Desktop through **interfaces**, typed function signatures written in a language called **WIT** (WebAssembly Interface Types). Think of a WIT file as a `.d.ts` file or an OpenAPI spec: it says what can be called and with what types, and nothing more. Desktop's agent interfaces are a package named `cosmonic:agent@0.3.0`. It is published to `ghcr.io/cosmonic-labs/cosmonic/agent`, much like an npm package is published to the npm registry. Point `wash` at the registry mapping, then fetch the package: ```shell export WKG_CONFIG_FILE=../../wit/wkg-registries.toml wash wit fetch ``` `wash` reports `WIT dependencies fetched successfully`, and a new directory appears at `wit/deps/cosmonic-agent-0.3.0`. Keep the `export` in place for the rest of this tutorial. `wash build` fetches the interfaces again, and fails with `no registry configured for namespace "cosmonic"` if the variable is not set. Open `wit/world.wit` to see what the agent asks for: ```wit title="wit/world.wit" world minimal-agent { import cosmonic:agent/inference-types@0.3.0; import cosmonic:agent/models@0.3.0; import cosmonic:agent/chat@0.3.0; import cosmonic:agent/session@0.3.0; } ``` Four imports: the types a model call uses, a way to open a model, a way to chat with it, and a place to store the conversation. That is the agent's whole reach into the outside world. ### 3. Give the agent a personality Open `src/lib.rs` and find the system prompt near the top: ```rust title="src/lib.rs" const SYSTEM_PROMPT: &str = "You are a concise, helpful assistant."; ``` Change it to something you'll recognize in a reply: ```rust title="src/lib.rs" const SYSTEM_PROMPT: &str = "You are a cheerful ship's navigator. Answer briefly, and end every reply with a nautical sign-off."; ``` ### 4. Build the component ```shell wash build ``` The last line of output names the file you just built: ```text Successfully built component at: .../target/wasm32-wasip2/release/minimal_agent.wasm ``` That file is a **WebAssembly component**: a compiled program, under 300 KB here, that can only call the interfaces it imports. It plays the role a container image plays for a container, without a filesystem or operating system packed inside. ### 5. Check what the agent can reach Before you run code, you can read exactly what it is able to call: ```shell wash inspect target/wasm32-wasip2/release/minimal_agent.wasm ``` ```text world root { import cosmonic:agent/inference-types@0.3.0; import cosmonic:agent/models@0.3.0; import cosmonic:agent/chat@0.3.0; import cosmonic:agent/session@0.3.0; import wasi:http/types@0.3.0; import wasi:random/random@0.3.0; import wasi:io/poll@0.2.9; import wasi:clocks/monotonic-clock@0.2.9; import wasi:io/error@0.2.9; import wasi:io/streams@0.2.9; import wasi:cli/stdout@0.2.9; ... export wasi:http/handler@0.3.0; } ``` Read the list for what is missing. There is no `wasi:http/client` (it cannot send an HTTP request), no `wasi:sockets` (no network connections), and no `wasi:filesystem` (no files). The agent cannot reach any of those, because the component has no way to ask for them. The one export, `wasi:http/handler`, is how Desktop talks to the agent: it sends the agent HTTP requests, the way a platform invokes a serverless function. ### 6. Push the image to Desktop's registry Desktop runs agents from images in an **OCI registry**, the same kind of registry Docker Hub is. Desktop has one built in, reachable at `oci.localhost:8200` while Desktop is running, so you don't need an account anywhere. ```shell wash oci push --insecure oci.localhost:8200/apps/minimal-agent:0.1.0 \ target/wasm32-wasip2/release/minimal_agent.wasm ``` `wash` prints `OCI command executed successfully.` The `--insecure` flag is there because the built-in registry speaks plain HTTP on your own machine. :::note[On Windows before Windows 10 version 1709] If `oci.localhost` does not resolve, use `oci.localhost.cosmonic.sh:8200/apps/minimal-agent:0.1.0` for the push and in the next step. It is the same registry under a public name that points to your own machine. ::: ### 7. Add the agent to Desktop 1. In Desktop, select **Agents** under **AI & agents** in the sidebar. The **Start a session** screen opens. If you already have sessions, select **New** at the top of the session list first. 2. In the **Agent** dropdown, choose **Add a new agent…**. A form opens in place. 3. Fill in the form: - **Name**: `Navigator` - **Image**: `oci.localhost:8200/apps/minimal-agent:0.1.0` - **Tools**: leave empty. This agent calls no tools. 4. Select **Check and save**. Desktop reads the image's imports, the same list you saw in step 5, and checks each one against the interfaces a sandboxed agent may use. The minimal agent passes, the form closes, and **Navigator** is now selected in the **Agent** dropdown. Under it Desktop says: _Your agent, from `oci.localhost:8200/apps/minimal-agent:0.1.0`. It runs with no network access of its own._ If the check fails, nothing is saved and the form lists each import or export that is the reason. ### 8. Start a session 1. Under **Model**, choose a source in the first dropdown (for example **Ollama on this machine**, or **Add a source…** to enter an API key), then choose a model in the second. There is no **Network** choice: it appears only when a session's tools can reach a host, and this agent has no tools. 2. In the message box at the bottom, type: ```text My name is Ada. Please remember it. ``` 3. Press Enter. Desktop starts a **session**: a fresh workload running your agent, with its own conversation store. Once the agent answers Desktop's readiness check, your message is sent and the reply streams in as the model writes it, ending in a nautical sign-off. The model call did not come from the agent. The agent asked Desktop for its model by the name `default`, and Desktop resolved that to the source and model you picked, holding the key itself. ### 9. Check that the conversation persists 1. Send a second message: ```text What is my name? ``` The agent answers with **Ada**. It remembered because each turn is saved in the session's store, not in the agent's memory. 2. Select **New** at the top of the session list, then select your session in the list again. The whole conversation is redrawn. Desktop draws it from the **session journal**, a log the host keeps for this session, which your agent writes every message into. When you end the session, the journal is deleted with it. ### What just happened You ran code you built as an agent, and at no point did it hold anything worth stealing: - **No network, filesystem, or key.** Its imports had no way to ask for them, and Desktop checked that before saving it. - **Model calls go through the host.** The agent names an alias, `default`. Desktop decides which model that means and makes the call with its own credentials. - **The conversation belongs to the session.** The host scopes the store to this one session, so no other workload can read it. None of this makes the agent's own code trustworthy, and it does not stop a model from being talked into a bad answer. What it limits is what the agent can reach when that happens. ### Change and rebuild When you change the code, push under a **new tag**. Desktop pins each image to the exact build a tag pointed to, so pushing a new build under `0.1.0` again would leave the old build running. ```shell wash build wash oci push --insecure oci.localhost:8200/apps/minimal-agent:0.1.1 \ target/wasm32-wasip2/release/minimal_agent.wasm ``` Then point Desktop at it: 1. Open **Settings**, then **Sandboxed agents**. 2. In the **Your agents** card, select **Edit** next to **Navigator**. 3. Change **Image** to `oci.localhost:8200/apps/minimal-agent:0.1.1` and select **Check and save**. 4. Start a new session with **Navigator**. Sessions you already started keep the image they started with, and Desktop stops sending them messages once the image changes. ### If something goes wrong - **The session never gets past starting.** Desktop waits up to 90 seconds for the agent to answer its readiness check. Select **Open logs** on the session to read what the agent printed. - **The conversation is missing when you come back.** The agent is streaming replies without writing them to its session journal. The [agent protocol](/desktop/agent-protocol#the-conversation) covers what to write. - **Check and save refuses the image.** Read the reasons under the form. An import of `wasi:http/client`, `wasi:sockets`, or `wasi:filesystem` is the usual cause; the [import allowlist](/desktop/agent-protocol#imports-and-exports) lists what is allowed. ### Takeaways - A sandboxed agent is one WebAssembly component that Desktop talks to over HTTP. - Its imports are its entire reach, and you can read them with `wash inspect` before you run it. - You add your own agent with **Add a new agent…** in the **Agent** dropdown, and manage it later under **Settings → Sandboxed agents**. - Push every rebuild under a new tag. ### Next steps - Read the [agent protocol](/desktop/agent-protocol) for every request, response line, journal entry, and limit your agent has to handle, including how to give it tools and how [tools keep their own state](/desktop/agent-protocol#durable-state-for-tools). - Browse the [`cosmonic:agent` interfaces](https://github.com/cosmonic-labs/awesome-cosmonic/tree/main/wit/cosmonic-agent) to see what `chat`, `session`, and `tools` offer. - Build the tools your agent calls the same way you'd build any sandboxed tool server, in [Sandbox MCP servers](/desktop/sandbox-mcp-servers). --- ## Build an AI Sandbox from Scratch: Hello World in Rust with wash Walkthroughs [1](/desktop/quickstart) and [2](/desktop/sandboxing-ai) used the app. This one doesn't. You'll build the same kind of sandboxed workload by hand—a Rust HTTP component, from an empty directory to a running server—using nothing but [`wash`](https://github.com/wasmCloud/wasmCloud), the wasmCloud shell. The point isn't the hello world. It's that halfway through, you'll run one command that prints the component's **complete** capability surface, and then watch that surface change when you add three words of code. That list is what an AI sandbox is actually made of. Everything the app does on top—the review step, the deny-by-default egress, the digest pinning—is policy layered over this one property. ### What you'll need - **`wash` 2.6.1 or later.** Install it from the [wasmCloud installation guide](https://wasmcloud.com/docs/installation); check with `wash --version`. - **A Rust toolchain**, plus the WebAssembly target: ```shell rustup target add wasm32-wasip2 ``` - **Cosmonic Desktop**, for the last step only. Steps 1–7 are pure `wash`. `wash` is the CNCF wasmCloud project's CLI. It's independent of Cosmonic Desktop—the components it builds are plain WebAssembly components, which is exactly why the boundary you'll see here is the same one Desktop enforces. ### 1. Install wash and the Rust wasm target Confirm both are in place before you start: ```shell wash --version rustup target list --installed | grep wasm32-wasip2 ``` ### 2. Scaffold the project `wash new` clones a template out of a git repository. The wasmCloud repo ships the HTTP hello world: ```shell wash new https://github.com/wasmCloud/wasmCloud.git \ --name hello-world \ --subfolder templates/http-hello-world cd hello-world ``` You get a small, complete project: ```text hello-world/ ├── .wash/ │ └── config.yaml # how wash builds this project ├── manifests/ │ └── workloaddeployment.yaml ├── src/ │ └── lib.rs # the handler ├── wit/ │ └── world.wit # the component's interface contract ├── Cargo.toml ├── Cargo.lock └── wkg.lock # pinned WIT dependencies ``` ### 3. Read the code Three files matter. Start with `src/lib.rs`: ```rust use wstd::http::{Body, Request, Response, StatusCode}; #[wstd::http_server] async fn main(req: Request) -> Result, wstd::http::Error> { match req.uri().path() { "/" => home(req).await, _ => not_found(req).await, } } async fn home(_req: Request) -> Result, wstd::http::Error> { Ok(Response::new("Hello from wasmCloud!\n".into())) } ``` Ordinary async Rust. No runtime to configure, no server to bind. The `#[wstd::http_server]` macro exports `wasi:http/incoming-handler`, which is the interface that makes this thing callable over HTTP. `wit/world.wit` is the interface contract, and it's nearly empty: ```wit package wasmcloud:hello; world hello { } ``` Empty because the macro handles the export. When you need a capability the macro doesn't provide—key-value storage, logging, outbound HTTP—you declare it as an **import** here, and that declaration is what the runtime grants against. `.wash/config.yaml` is the whole build configuration: ```yaml build: command: cargo build --target wasm32-wasip2 --release component_path: target/wasm32-wasip2/release/hello_world.wasm ``` `wash` shells out to `cargo`. There's no hidden build system. ### 4. Build the component ```shell wash build ``` ```text Finished `release` profile [optimized] target(s) in 5.64s Successfully built component at: …/target/wasm32-wasip2/release/hello_world.wasm ``` That `.wasm` file is around 350 KB and it is the entire deployable artifact—no base image, no interpreter, no runtime bundled alongside it. It's also the unit the sandbox is enforced against, which is what makes the next step possible. ### 5. Inspect the sandbox boundary This is the step the whole walkthrough exists for: ```shell wash inspect target/wasm32-wasip2/release/hello_world.wasm ``` ```text package root:component; world root { import wasi:io/poll@0.2.9; import wasi:clocks/monotonic-clock@0.2.9; import wasi:io/error@0.2.9; import wasi:io/streams@0.2.9; import wasi:cli/stdout@0.2.9; import wasi:cli/stderr@0.2.9; import wasi:cli/stdin@0.2.9; import wasi:http/types@0.2.9; import wasi:cli/environment@0.2.9; import wasi:cli/exit@0.2.9; import wasi:cli/terminal-input@0.2.9; import wasi:cli/terminal-output@0.2.9; … import wasi:random/insecure-seed@0.2.9; export wasi:http/incoming-handler@0.2.9; } ``` Read what is **not** there. There is no `wasi:filesystem`. There is no `wasi:sockets`. This component cannot open a file or a socket—not because a policy forbids it, not because a seccomp filter intercepts the syscall, but because the machinery to express the request doesn't exist inside the binary. A WebAssembly component has no ambient authority: it can only call functions its host explicitly supplied, and it can only receive functions it declared as imports. That's the difference from a container. A container starts with a filesystem, a network stack, and a process tree, and hardening means subtracting from that set—which is why a missed `--cap-drop` or a permissive seccomp profile is a real vulnerability class. A component starts with an empty set and every element you see above was added deliberately. There is no equivalent of "forgot to drop a capability," because nothing was granted to begin with. The one-line version: **the import list is the sandbox.** And you didn't write it—the compiler derived it from the code. ### 6. Watch the boundary change Don't take the previous step's word for it. Make the code reach for something and watch the manifest move. Edit `src/lib.rs` so `home` touches the filesystem: ```rust async fn home(_req: Request) -> Result, wstd::http::Error> { let who = std::fs::read_to_string("/etc/hostname").unwrap_or_else(|_| "nobody".into()); Ok(Response::new(format!("Hello from {who}\n").into())) } ``` Rebuild and inspect again: ```shell wash build && wash inspect target/wasm32-wasip2/release/hello_world.wasm ``` Two imports you didn't have before: ```text import wasi:filesystem/types@0.2.9; import wasi:filesystem/preopens@0.2.9; ``` Nothing was declared. No manifest was edited. The capability appeared in the artifact the moment the code needed it, because `std::fs` on `wasm32-wasip2` compiles down to WASI calls that must be imported by name. This is why the capability list on a workload spec is worth trusting in a way a hand-written permissions file isn't. It can't drift from the code, and it can't be understated by an author—or by an agent—who'd rather not mention what their component does. A reviewer reading that list is reading the binary, not a claim about it. :::tip[Try it with egress] Swap the filesystem read for an outbound HTTP call and `wasi:http/outgoing-handler` appears the same way. That import is precisely what Cosmonic Desktop's deny-by-default `allowedHosts` list governs—a component without it cannot make an outbound request at all, and a component with it can only reach the hosts you named. ::: Revert the change before continuing: ```shell git checkout src/lib.rs # or undo the edit by hand ``` ### 7. Run it `wash dev` builds the component, starts a host, serves it, and rebuilds on every file change: ```shell wash dev ``` ```text INFO HTTP server listening addr=0.0.0.0:8000 protocol="HTTP" INFO building component path="…/hello-world" INFO listening for HTTP requests address=http://127.0.0.1:8000 ``` From another terminal: ```shell curl localhost:8000 ``` ```text Hello from wasmCloud! ``` Note what the host logs on startup: it lists the interfaces it _provides_, including `wasi:filesystem` and `wasi:sockets`. The host offers them; your component never asked for them, so it never gets them. Supply and demand are negotiated per component, which is what lets a single host run mutually untrusting workloads side by side. Edit `src/lib.rs` while `wash dev` is running and it rebuilds in about a second. Stop it with Ctrl+C. ### 8. Promote it to a real sandbox `wash dev` is a development loop—it stops when you close the terminal. To get a durable, digest-pinned workload with reviewable policy, hand the component to Cosmonic Desktop. Push it to any OCI registry you can write to: ```shell wash oci push ghcr.io//hello-world:0.1.0 \ target/wasm32-wasip2/release/hello_world.wasm ``` Then take it through the same flow as [walkthrough 1](/desktop/quickstart#2-run-a-component): choose **Start workload** in Desktop, paste the reference, and review the draft. Desktop resolves the tag to an immutable digest, infers the capabilities from the very import list you inspected in step 5, and starts `allowedHosts` empty. For a fully offline path, `wash` can write an OCI image-layout directory that `cosmonicd oci import ` loads straight into the local content-addressed cache—no registry involved. See [the daemon's `oci` commands](/desktop/daemon#the-cosmonicd-cli). The `manifests/workloaddeployment.yaml` in the template is the Kubernetes-flavored sibling of that spec: the same `runtime.wasmcloud.dev/v1alpha1` API [Cosmonic Control](/) serves on a cluster. One artifact, one capability boundary, three places to run it. ### What to take away - A WebAssembly component's **imports are its capability surface**, and the compiler produces them from the code. Sandboxing is a property of the artifact, not a configuration you apply to it. - Because that surface is derived rather than declared, it's honest about code you didn't write—whether it came from a dependency, a registry, or a language model. - The platform's job isn't to create the boundary. It's to pin the artifact, make the boundary reviewable before the code starts, and enforce the policy you attach to it. ### Frequently asked questions ### Next steps - Go back to the app and let an agent do this loop for you: [Sandboxing AI](/desktop/sandboxing-ai). - Map the container concepts you already know onto components in [Containers to Components](/desktop/containers-to-components). - Take the same spec to a cluster in [From laptop to cluster](/desktop/laptop-to-cluster). - Package a tool server the same way in [Sandbox MCP Servers](/desktop/sandbox-mcp-servers). --- ## Air-gapped install Cosmonic Desktop normally installs the [build toolchain](/desktop/build-toolchain/) the first time you build, pulling `wash`, `wkg`, and `wasm-tools` from Cosmonic's namespace on the GitHub Container Registry (`ghcr.io`). On a machine with no internet access, you bring the same signed tools in an offline bundle instead. The trust model is unchanged: the bundle carries the cosign signatures, and the daemon verifies every tool against the key built into the app before installing it, exactly as it would online. ### How it works On a connected machine, the signed tool artifacts and their signatures are packed into a single OCI image-layout archive. You carry that archive to the air-gapped machine and import it into Desktop's local store. From then on the toolchain installs with no network: the tools are already present locally, and they still verify against Cosmonic's key. ### Prerequisites - A **connected** machine to build the bundle on. - The target **air-gapped** machine with Cosmonic Desktop installed. - The `cosmonicd` command (the Cosmonic Desktop daemon) on both. It ships inside the app; see [the daemon CLI](/desktop/daemon#the-cosmonicd-cli). ### 1. Build the bundle on a connected machine Match the platform to the air-gapped machine, for example `x86_64-unknown-linux-gnu`. For a mixed fleet, build one bundle covering every supported platform with `--all-platforms`. ```shell cosmonicd toolchain bundle --triple x86_64-unknown-linux-gnu # → toolchain-bundle-x86_64-unknown-linux-gnu.tar # --all-platforms → toolchain-bundle-all.tar ``` The bundle contains the signed `wash`, `wkg`, and `wasm-tools` artifacts for that platform plus their cosign signatures, each verified against Cosmonic's key as it is packaged, so a bundle never carries a tool that would not install. It does not contain the Rust toolchain. Install Rust from your internal mirror instead (see [Restricted networks](/desktop/build-toolchain/#restricted-networks-and-offline-machines)). ### 2. Transfer and import on the air-gapped machine Copy the archive across through your usual transfer process, then import it: ```shell cosmonicd oci import toolchain-bundle-x86_64-unknown-linux-gnu.tar ``` The import verifies every blob against its digest as it loads, and never reaches the network. ### 3. Install In Cosmonic Desktop, open **Settings → Toolchain**, select **Re-check**, then install. Desktop finds `wash`, `wkg`, and `wasm-tools` in the imported store, verifies each against Cosmonic's key, and installs them with no network access. ### Building still resolves project dependencies Installing the toolchain offline does not make a *build* fully offline. Compiling a component still resolves its own dependencies, and those come from registries: a Rust build fetches crates from crates.io, and a Go or TypeScript build may resolve WIT interface definitions (through `wkg`) and language packages from their registries. On an air-gapped machine, satisfy those ahead of time (a warm `~/.cargo` cache or vendored crates for Rust, or vendored `wit/deps` for a component that resolves WIT at build), or point each tool at an internal mirror. This bundle covers the signed *toolchain*, not your project's dependency graph. ### Keeping it current When a Desktop update raises the pinned tool versions, rebuild the bundle on the connected machine and import it again. The bundler will not overwrite an existing `.tar`, so remove or rename the previous one first, or pass a new `--out` path. A stale bundle is harmless: the daemon installs only what verifies against the current pinned digests, so an out-of-date bundle is ignored until you refresh it. ### Takeaways - Air-gapped installs use the same signed tools and the same verification as online installs. - Build the bundle on a connected machine, transfer it, and `cosmonicd oci import` it. - Match the bundle's platform to the target, or bundle every platform for a fleet. - Install Rust from an internal mirror; the bundle covers `wash`, `wkg`, and `wasm-tools`. - The bundle installs the toolchain offline, but a build still resolves your crates and WIT deps; vendor or mirror those too. --- ## The build toolchain To turn your code into a running component, Cosmonic Desktop needs a small set of build tools on your machine. The first time you build, Desktop checks for them and offers to install anything missing, so you rarely have to think about them. This page explains what the toolchain is, how Desktop installs and verifies it, and what a ready-to-build machine looks like. ### What's in the toolchain | Tool | What it does | |---|---| | **Rust toolchain** | The compiler (`rustc`) and `cargo` that build your component from source. | | **`wasm32-wasip2` target** | The compile target that produces a portable, sandboxed component. | | **`wash`** | The wasmCloud command-line tool. Desktop uses it to build your component and run it on the local host as you develop. | | **`wkg`** | The package tool that fetches the interface definitions your component builds against. | | **`wasm-tools`** | The WebAssembly component toolkit. An advisory extra Desktop provisions for the componentize-go / WASI-p3 Go path; a Rust or JavaScript build does not need it, so a machine without it still counts as ready. | | **A C linker** | A system C linker, such as the Xcode Command Line Tools on macOS, `gcc` or `clang` on Linux, or the MSVC Build Tools on Windows. Even a WebAssembly build compiles a little host code that needs one. | ### How Desktop installs it The first time a build needs the toolchain, Desktop shows an **Install build toolchain** offer. Accept it, and Desktop installs whatever is missing into an app-owned location, so it never changes a Rust setup you manage yourself. You can install, re-check, or remove the toolchain any time from **Settings → Toolchain**. `wash`, `wkg`, and `wasm-tools` do not come from a plain download. Desktop pulls them as cosign-signed artifacts from Cosmonic's namespace on the GitHub Container Registry (`ghcr.io`) and verifies each one against a key built into the app before installing it. If a signature does not verify, the install stops instead of running an unverified tool. Desktop also keeps the toolchain current for you. When an app update raises the pinned versions, Desktop re-installs the tools it manages in the background. It never touches a tool you brought yourself. ### Using your own toolchain If you already have Rust, `wash`, or `wkg`, Desktop uses them. You do not need `rustup`, and you do not need to match the exact versions Desktop pins. A different working version shows up as a note, not a problem. Desktop only manages the tools it installed. ### What satisfies the readiness check The readiness check passes as long as nothing is a hard failure. It looks at the tools above and blocks only when one is genuinely missing. Most gaps are one click to fix from **Settings → Toolchain**. The one exception is the C linker, which you install yourself. A machine is **ready to build** when it has: - Rust installed (any version), - the `wasm32-wasip2` target available, - a C linker present, - `wash` installed (any version), - `wkg` installed (any version). A machine is **not ready** when: - **Rust is missing.** Desktop installs it for you. - **The `wasm32-wasip2` target was never added** to your Rust toolchain. If your Rust is managed by `rustup`, Desktop adds it; a non-`rustup` Rust is assumed to already include the target. - **No C linker is found.** This is the one thing Desktop cannot install. On macOS, run `xcode-select --install`. On Linux, install a C toolchain such as `build-essential` (Debian, Ubuntu) or `gcc` (Fedora). Then re-check. - **`wash` or `wkg` is missing.** Desktop installs them from the signed artifacts. Version differences never block a build. Only a missing tool does. ### Restricted networks and offline machines On a locked-down network, point Rust at an internal mirror by setting `RUSTUP_DIST_SERVER` and `RUSTUP_UPDATE_ROOT`, then re-check. The signed `wash`, `wkg`, and `wasm-tools` tools install from `ghcr.io`, so route that through your registry mirror. On a machine with **no** network access, install the toolchain from a signed bundle instead. See [Air-gapped install](/desktop/build-toolchain/air-gapped/). ### Takeaways - Desktop installs the build toolchain for you the first time you build, and keeps it current. - `wash`, `wkg`, and `wasm-tools` are cryptographically verified before they install. - The check blocks only on a genuinely missing tool. Everything but the C linker is one click from **Settings → Toolchain**. - Bring your own toolchain if you prefer. Version differences are fine. --- ## The cosmonic CLI `cosmonic` is the developer command-line tool for Cosmonic Desktop. Use it to scaffold a project and run the build-and-run loop from your terminal. It is the by-hand counterpart to the [Build view](/desktop/app-views/). `cosmonic` is a thin client. Each command sends a request to Cosmonic Desktop's background daemon, and the daemon does the work. Keep Cosmonic Desktop running while you use the CLI. `cosmonic` is a different program from `cosmonicd`, the daemon itself. For the daemon and its `cosmonicd` subcommands, see [The cosmonicd Daemon](/desktop/daemon/). You do not install `cosmonic` yourself. Cosmonic Desktop ships it and puts it on your `PATH`. ### cosmonic new `cosmonic new` scaffolds a project from a template that ships inside Cosmonic Desktop. The templates are built in, so scaffolding needs no network and no other tools. ```bash cosmonic new