Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Run cosmon as a remote service

Goal: run a cosmon service on a remote GPU box and drive it from your own machine with cosmon-remote. This is useful for an invited-guest host: a machine you operate but may not own, where you have no root access and cannot use Docker.

The client never needs an interactive shell on the service host. It talks to one HTTP entry point (the fente); an SSH tunnel is only an L0 transport that makes that entry point local to the client. Configure ProxyJump in your SSH config if <remote> is behind a bastion.

Remote service topology. On your machine, the cosmon-remote thin client has no interactive shell on the host. It crosses one HTTP entry point, optionally carried by an SSH tunnel at L0, into a remote box you operate. There, cosmon-rpp-adapter is the fente and HTTP service; it delegates to cs tackle, which runs a local model. The remote host therefore runs a server.
Remote mode has one HTTP doorway; lifecycle work and the model stay on the host you operate.

This guide uses a demo identity provider so that the complete auth path is reproducible. Replace it with your production issuer before exposing the service beyond a private tunnel. For how this service surface relates to the runtime, see Control plane vs data plane.

What to deploy

The public noogram/cosmon product closure contains both service components:

CrateRoleLicense
cosmon-rpp-adapterThe HTTP fente and /v1/... service surface.AGPL-3.0-only
cosmon-remoteThin client that drives the service.AGPL-3.0-only

The two host-side binaries — cosmon-rpp-adapter (the fente) and cs-oidc-mock (the demo IdP used in Step 2) — ship as a signed cosmon-service-<version>-<target>.tar.gz release asset per target, alongside the cs CLI tarball. Step 1 therefore has two routes: download the signed release bundle (no Rust toolchain required), or build from source. Prefer the download route unless you need an unreleased revision.

The client, cosmon-remote, is a laptop tool, so it ships with cs: the cosmon-<version>-<target>.tar.gz release tarball carries both, and the one-liner installer (curl -fsSL https://noogram.org/cosmon/install.sh | sh) and the Homebrew formula each place cs and cosmon-remote together. If you installed cs, you already have the connector — there is no separate client fetch. The steps below cover only the host; run the cosmon-remote commands from Step 4 on the machine where you installed cs.

The service delegates work to cs tackle; it is not a second scheduler. On the host, cs resolves the selected worker adapter. For this setup it uses the built-in local adapter: an in-process Ollama /v1 client. No Node.js, Claude runtime, or tmux session is required for that worker leg.

Step 1: Get the binaries and prepare the remote galaxy

You need three binaries on the host: cosmon-rpp-adapter, cs-oidc-mock, and a compatible cs. Get them the signed-release way (no toolchain) or build them from source.

First, make the target directory on the host. This guide calls it $COSMON_HOME.

ssh <remote> "mkdir -p '$COSMON_HOME/bin' '$COSMON_HOME/state/security' '$COSMON_HOME/galaxies'"

Each release ships a cosmon-service-<version>-<target>.tar.gz (the fente + demo IdP) next to the cs CLI tarball. Pick the target matching the host — a static Linux box uses x86_64-unknown-linux-musl or aarch64-unknown-linux-musl. Both tarballs are cosign-signed and Rekor-anchored; verify them as in Verify the binary's provenance.

ver=0.1.0
target=x86_64-unknown-linux-musl
base="https://github.com/noogram/cosmon/releases/download/v${ver}"
curl -fsSLO "${base}/cosmon-service-${ver}-${target}.tar.gz"   # cosmon-rpp-adapter + cs-oidc-mock
curl -fsSLO "${base}/cosmon-${ver}-${target}.tar.gz"           # cs
tar xzf "cosmon-service-${ver}-${target}.tar.gz"
tar xzf "cosmon-${ver}-${target}.tar.gz"
scp cosmon-rpp-adapter cs-oidc-mock cs <remote>:$COSMON_HOME/bin/

Route B — build from source

Build a static musl release on your development machine. cargo-zigbuild uses Zig as the cross-linker, so this works without a Linux container. Install its prerequisites first (Rust's musl target, Zig, and cargo-zigbuild).

cd /path/to/cosmon
cargo zigbuild --release --target x86_64-unknown-linux-musl \
  -p cosmon-rpp-adapter --bin cosmon-rpp-adapter
cargo zigbuild --release --target x86_64-unknown-linux-musl \
  -p cosmon-oidc-testkit --bin cs-oidc-mock

Copy the resulting binaries, plus a compatible cs binary, to $COSMON_HOME/bin.

scp target/x86_64-unknown-linux-musl/release/cosmon-rpp-adapter \
    target/x86_64-unknown-linux-musl/release/cs-oidc-mock \
    /path/to/compatible/cs <remote>:$COSMON_HOME/bin/

Install or configure Ollama on the remote host and make a model that fits its VRAM available. Initialise the demo galaxy and select the local adapter so cs tackle selects Ollama rather than an external coding-agent adapter:

ssh <remote> '
  "$COSMON_HOME/bin/cs" init "$COSMON_HOME/galaxies/demo" --tenant demo
  cat >> "$COSMON_HOME/galaxies/demo/.cosmon/config.toml" <<'"'"'EOF'"'"'

[adapters]
default = "local"

[adapters.local]
default_model = "<your-model>"
EOF
'

Step 2: Start the demo identity provider and pin its keys

cs-oidc-mock is a small demo IdP. Its defaults are issuer https://idp.test.cosmon-oidc-testkit, audience cosmon-rpp-test, a 10-minute token lifetime, and bind address 0.0.0.0:8444. This guide binds it to loopback on port 8444, writes its JWKS, and uses its POST /issue route to mint signed test tokens:

ssh <remote> '
  $COSMON_HOME/bin/cs-oidc-mock \
    --bind 127.0.0.1:8444 \
    --write-jwks-out $COSMON_HOME/state/security/jwks/idp.json
'

In a second remote shell, declare the matching issuer and render the demo identity's binding. The issuer, audience, and subject must agree with the token minted below:

ssh <remote> '
  cat > "$COSMON_HOME/state/security/trusted-issuers.toml" <<'"'"'EOF'"'"'
[[issuer]]
iss = "https://idp.test.cosmon-oidc-testkit"
jwks_uri = "http://127.0.0.1:8444/jwks.json"
audiences = ["cosmon-rpp-test"]
EOF

  mkdir -p "$COSMON_HOME/state/nucleons/demo"
  "$COSMON_HOME/bin/cosmon-rpp-adapter" nucleon render \
    --noyau demo --sub demo-operator \
    --iss https://idp.test.cosmon-oidc-testkit --aud cosmon-rpp-test \
    --scope cosmon:molecule:read --scope cosmon:molecule:write \
    > "$COSMON_HOME/state/nucleons/demo/oidc-identity.toml"
'

The pinned JWKS and this nucleon binding are both required: a valid token alone does not grant access to a tenant.

For a production deployment, replace the mock with your production IdP, pin its JWKS under $COSMON_HOME/state/security, and keep the issuer and binding rules explicit.

Step 3: Start the fente on loopback

Start cosmon-rpp-adapter with its state directory, tenant configuration, and the loopback address that the tunnel will reach. Keep this process supervised by the service manager available to the host.

ssh <remote> '
  $COSMON_HOME/bin/cosmon-rpp-adapter \
    --bind 127.0.0.1:8443 \
    --config $COSMON_HOME/rpp.toml
'

The rpp.toml config declares the state directory, the tenant galaxies root, and the path to the cs binary — for example:

bind_addr = "127.0.0.1:8443"
state_dir = "/opt/cosmon/state"
galaxies_root = "/opt/cosmon/galaxies"
cs_path = "/opt/cosmon/bin/cs"
artifact_root = "/opt/cosmon/artifacts"

The service is deliberately loopback-only here. The tunnel is the sole L0 path to its HTTP surface; the client does not use a direct shell or container-exec path to create, tackle, or fetch work.

Step 4: Open the tunnel, mint a demo token, and create a client profile

On the client machine, open a tunnel. It maps the remote service's loopback port to a local port. The mock remains loopback-only on the remote host, so mint its short-lived token server-side and pass it to the client; this avoids needing a second IdP tunnel:

ssh -f -N -L 127.0.0.1:8443:127.0.0.1:8443 <remote>

TOKEN=$(ssh <remote> '
  curl --fail --silent --show-error -X POST \
    "http://127.0.0.1:8444/issue?sub=demo-operator&aud=cosmon-rpp-test&scopes=cosmon:molecule:read,cosmon:molecule:write" \
    | jq -r .access_token
')
export COSMON_REMOTE_TOKEN="$TOKEN"

cosmon-remote stores a default profile and one profile file per service. Resolution is --profile first, then $COSMON_REMOTE_PROFILE, then the configured default.

Create the profile manually (or use the service's install.sh profile installer when your deployment provides one). oidc-url remains required by a profile, but the pre-minted token means this client does not need to reach it:

cosmon-remote config init demo http://127.0.0.1:8443
cosmon-remote config set host http://127.0.0.1:8443
cosmon-remote config set sub demo-operator
cosmon-remote config set aud cosmon-rpp-test
cosmon-remote config set oidc-url http://127.0.0.1:8444
cosmon-remote config set issuer https://idp.test.cosmon-oidc-testkit
cosmon-remote config set client-id cosmon-rpp-test
cosmon-remote config set noyau demo
cosmon-remote config set timeout 30
cosmon-remote config set artifacts-dir ./cosmon-artifacts
cosmon-remote config set phone-home off

Verify both liveness and the identity that the service resolved:

cosmon-remote healthz
cosmon-remote auth me

Step 5: Drive the measured golden path

From the thin client, create a molecule, dispatch it, wait for its detached worker to finish, and retrieve its artifact:

cosmon-remote do --yes "write a Rust function that returns the nth Fibonacci number"
cosmon-remote artifact list <molecule-id>
cosmon-remote artifact get <molecule-id> <artifact-token> --out ./result.md

The do gesture performs nucleate then tackle; tackle returns a worker session promptly while the local Ollama worker runs detached on the remote host. The successful path is:

thin client -> tunnel -> fente -> cs tackle -> local Ollama worker
             <- artifact get <- completed molecule <- detached worker

This confirms the full service contract: a profile-authenticated thin client creates work over the tunnel, the remote service dispatches the local adapter, the molecule reaches completed, and the client receives the resulting artifact without an interactive remote execution path.

Addressing artifacts

artifact list prints opaque artifact tokens such as art_...; use the token, not a server path, with artifact get <molecule-id> <artifact-token>. During cs tackle, the adapter creates <artifact_root>/<noyau>/<molecule-id>/ and exports that directory as $COSMON_ARTIFACT_DIR to the worker. The client fetches bytes with --out; when omitted, it writes under ./cosmon-artifacts/<molecule-id>/<artifact-token>.

Troubleshooting and security

On a sovereign local-adapter host, a 503 tackle_unavailable message mentioning “Claude Code not installed” is an expected default-container diagnostic, not a requirement for the local Ollama path. An empty artifact list normally means the worker has not written its deliverable or has not completed yet. A 401 or 403 from auth me means to compare the token's issuer, subject, audience, and scopes with trusted-issuers.toml and the rendered nucleon binding.

Security: the local worker is sandboxed for untrusted work: it receives a six-tool, shell-free registry rather than host-shell access; a toolchain preflight runs before work; and each molecule has a wall-clock limit. It cannot use that worker interface to scan the host or read outside its worktree.

See also