Install this skill

Paste this into Claude Code (VS Code panel, Adom editor, or terminal) to install:

Search the Adom Wiki for the skill "Curium" (slug: curium) at https://wiki-ufypy5dpx93o.adom.cloud/wiki/skills/curium and install it into my local ~/.claude/skills/curium/ directory. Fetch the skill_source content from the wiki page and save it as SKILL.md. Then confirm it's installed by showing the first 5 lines.
?
What is a skill? Skills are instructions that teach AI assistants like Claude Code how to perform specific tasks. The description below is loaded into the AI as context when you invoke this skill. Well-written skills make the AI significantly more effective. Like Wikipedia, anyone can improve a skill by clicking Edit AI Skill — or have your AI submit an edit on your behalf.

Description

Edit AI Skill

Curium API -- Adom Container Management

Curium is the Adom container orchestration service. It manages Docker containers, Traefik routing, SSH portal access, and USB/IP device attachment for user project containers.

Base URL: https://curium.adom.inc (or wherever your Curium instance is hosted)

Authentication

Curium itself currently has minimal auth (TODOs in codebase). It integrates with Carbon for user/org lookups and API key creation. When calling Curium from within an Adom container, use the standard Adom API key pattern:

API_KEY=$(cat /var/run/adom/api-key)
# Then use: -b "session_token=$API_KEY" on curl calls if auth is added

Container ID Format

All containers use the format: {owner}-{repository}-{hex_hash}

Example: john-gallia-f280e93ffec7e79d

  • owner: project owner username
  • repository: project/repo name
  • hex_hash: 8-byte random hex identifier

Container Endpoints

List Containers

GET /containers

Query Parameters (optional):

ParamTypeDescription
ownerstringFilter by owner username
repositorystringFilter by repository name

Response (200):

[
  {
    "id": "john-gallia-f280e93ffec7e79d",
    "owner": "john",
    "repository": "gallia",
    "unique_hash": "f280e93ffec7e79d",
    "status": "running",
    "services": {
      "coder_url": "https://coder.john-gallia-f280e93ffec7e79d.containers.adom.inc",
      "s3_url": "https://s3.john-gallia-f280e93ffec7e79d.containers.adom.inc",
      "ssh_credentials": {
        "hostname": "ssh.containers.adom.inc",
        "port": 2222,
        "username": "john-gallia-f280e93ffec7e79d"
      }
    }
  }
]

Create Container

POST /containers

Request Body:

{
  "owner": "john",
  "repository": "gallia",
  "actor": "john",
  "resource_limits": {
    "cpu_shares": 512,
    "vcpu_count": 2.0,
    "memory_soft_limit": 536870912,
    "memory_hard_limit": 1073741824
  }
}
FieldTypeRequiredDefaultDescription
ownerstringyes--Project owner username
repositorystringyes--Project/repo name
actorstringyes--User performing the action
resource_limits.cpu_sharesintno512Relative CPU priority
resource_limits.vcpu_countfloatno2.0vCPU allocation (0.1-8)
resource_limits.memory_soft_limitintno536870912Memory reservation in bytes (512 MiB)
resource_limits.memory_hard_limitintno1073741824Memory max in bytes (1 GiB). Range: 128 MiB - 8 GiB

Response (201):

{
  "id": "john-gallia-f280e93ffec7e79d",
  "owner": "john",
  "repository": "gallia",
  "unique_hash": "f280e93ffec7e79d",
  "services": { "..." }
}

What happens internally:

  1. Fetches user orgs from Carbon (/users/{actor}/orgs)
  2. Creates API key via Carbon (/internal/containers/create-api-key)
  3. Copies project files from {DATA_DIR}/public/projects/{owner}/{name}/latest
  4. Creates isolated Docker bridge network named {container_id}
  5. Connects Traefik to the new network
  6. Creates container with Traefik labels for Coder (port 8080) and S3 (port 9000)
  7. Mounts project dir at /home/adom/project, API key at /var/run/adom/api-key
  8. Starts the container

Get Container

GET /containers/{id}

Response (200): Container object with status. Returns 404 if not found.

Delete Container

DELETE /containers/{id}?commit_state={bool}
ParamTypeRequiredDescription
commit_stateboolyesIf true, saves container state before deletion

Response (204)

When commit_state=true:

  • Backs up current latest/ to history/{uuid}.tar.gz
  • Replaces latest/ with current container filesystem

Then: force-kills container, removes volumes, disconnects Traefik, deletes network.

Pause Container

POST /containers/{id}/pause

Response (204). Suspends all processes without removing the container.

Resume Container

POST /containers/{id}/resume

Response (204). Resumes a paused container.

Stop Container

POST /containers/{id}/stop

Response (204). Stops container without removing it.

Start Container

POST /containers/{id}/start

Response (204). Starts a stopped container.

Commit Container State

POST /containers/{id}/commit

Response (204). Creates a backup archive and replaces latest/ with current filesystem.

Stream Container Stats

GET /containers/{id}/stats

Without SSE: Returns a single JSON stats snapshot (Docker ContainerStatsResponse format: CPU, memory, network I/O, block I/O, PIDs).

With SSE (set Accept: text/event-stream): Streams stats as SSE events:

event: stats
data: {"cpu_stats": {...}, "memory_stats": {...}, ...}

Prune Containers

POST /containers/prune

Response (204). Cleans up:

  • Non-existent containers labeled adom-user-container
  • Disconnects Traefik from orphaned networks
  • Deletes orphaned networks
  • Removes stale working directories, Traefik configs, and TLS certs

Container Lifecycle

created --[start]--> running
running --[pause]--> paused
paused  --[resume]--> running
running --[stop]--> exited
exited  --[start]--> running
any     --[delete]--> removed

Workcell Endpoints

Workcells are physical hardware units (e.g., Raspberry Pis) with USB devices that can be associated with containers.

List Workcells

GET /workcells

Response (200):

[
  {
    "workcell_id": "raspberry-pi-001",
    "ip_address": "192.168.1.100",
    "connected_at": "2026-03-06T12:15:30Z",
    "container_id": "john-gallia-f280e93ffec7e79d",
    "associated_at": "2026-03-06T12:16:45Z",
    "bound_devices": [
      {
        "bus_id": "1-1",
        "bound_at": "2026-03-06T12:16:50Z",
        "dev_node": "/dev/ttyUSB0",
        "subsystem": "tty",
        "manufacturer": "Silicon Labs",
        "product": "CP2102 USB to UART Bridge Controller",
        "serial_number": "0001",
        "id_vendor": 1659,
        "id_product": 6015,
        "...": "additional device metadata"
      }
    ]
  }
]

Get Workcell

GET /workcells/{id}

Response (200): Workcell object with bound devices. Returns 404 if not found.

Associate Container with Workcell

PUT /workcells/{id}/association

Request Body:

{
  "container_id": "john-gallia-f280e93ffec7e79d"
}

Response (200): Updated workcell object.

Attaches all bound USB devices to the container via USB/IP.

Disassociate Container from Workcell

DELETE /workcells/{id}/association

Response (200): Updated workcell object (container_id becomes null).

Detaches all USB devices and cleans up USB/IP ports.

Workcell WebSocket (Device Notifications)

GET /workcells/usbip-notify  (WebSocket upgrade)

Used by workcells to report device hot-plug events:

Initial handshake (client sends):

{
  "workcell_id": "raspberry-pi-001",
  "ip_address": "192.168.1.100"
}

Device bound (client sends):

{
  "type": "device_bound",
  "bus_id": "1-1",
  "metadata": { "dev_node": "/dev/ttyUSB0", "manufacturer": "...", "..." }
}

Device removed (client sends):

{
  "type": "device_removed",
  "bus_id": "1-1"
}

Networking & Routing

Each container gets its own Docker bridge network. Traefik connects to each network and routes traffic based on hostname:

ServiceHostname PatternPort
Coder (VS Code)coder.{container_id}.{BASE_HOSTNAME}8080
S3s3.{container_id}.{BASE_HOSTNAME}9000
SSHssh.{BASE_HOSTNAME}:2222 (username = container_id)2222

Default BASE_HOSTNAME: containers.adom.inc (production) or containers.adom.localhost (dev).

TLS via Let's Encrypt on the websecure entrypoint (port 443).


Directory Structure on Host

{DATA_DIR}/
  public/
    containers/{owner}/{repo}/{hash}/    # Working dir -> /home/adom/project
    projects/{owner}/{repo}/
      latest/                            # Current project files
      history/{uuid}.tar.gz              # Committed snapshots
    molecules/{scope}/                   # Shared molecule dirs
  private/
    container-api-keys/{owner}/{repo}/{hash}  # -> /var/run/adom/api-key
    traefik/dynamic-config/              # Per-container routing rules
    traefik/dynamic-certificates/        # Per-container TLS certs

Resource Defaults

ResourceDefaultRange
CPU shares512--
vCPU count2.00.1 - 8
Memory soft limit512 MiB--
Memory hard limit1 GiB128 MiB - 8 GiB

Configuration (Environment Variables)

VarDefaultDescription
BIND_ADDR0.0.0.0Server bind address
PORT3000Server port
DATA_DIRECTORY_EXTERNAL--External data path
DATA_DIRECTORY_INTERNAL--Internal data path
BASE_HOSTNAMEcontainers.adom.localhostBase hostname for routing
USER_CONTAINER_IMAGEadom/user-containers/default:latestDocker image for containers
DOCKER_SOCKET_PATH/var/run/docker.sockDocker socket
KEEP_CONTAINER_WORKING_DIRECTORY--Set to 1 to preserve dirs on delete

Error Responses

{
  "code": 500,
  "error": "INTERNAL_SERVER_ERROR",
  "message": "descriptive error message"
}

Standard HTTP status codes: 200, 201, 204, 404, 500.


Quick Examples

Create a container:

curl -s -X POST https://curium.adom.inc/containers \
  -H 'Content-Type: application/json' \
  -d '{
    "owner": "john",
    "repository": "my-project",
    "actor": "john",
    "resource_limits": {
      "vcpu_count": 4.0,
      "memory_hard_limit": 4294967296
    }
  }'

Get container stats:

curl -s https://curium.adom.inc/containers/john-my-project-abc123def456/stats

Pause and resume:

curl -s -X POST https://curium.adom.inc/containers/john-my-project-abc123def456/pause
curl -s -X POST https://curium.adom.inc/containers/john-my-project-abc123def456/resume

Delete with state commit:

curl -s -X DELETE 'https://curium.adom.inc/containers/john-my-project-abc123def456?commit_state=true'

List containers for a user:

curl -s 'https://curium.adom.inc/containers?owner=john'

Associate a workcell:

curl -s -X PUT https://curium.adom.inc/workcells/raspberry-pi-001/association \
  -H 'Content-Type: application/json' \
  -d '{"container_id": "john-gallia-f280e93ffec7e79d"}'

Sub-Skills
?
What are Sub-Skills?

Sub-skills are community-contributed AI skill extensions for this component. They teach AI assistants about specific tools, configurators, or workflows.

Examples:

  • A manufacturer’s configuration tool for a motor controller
  • A community-written design guide for an amplifier circuit
  • An automated test/validation script for a sensor module

How to add one: Click Add Sub-Skill, provide the URL to your skill and a brief description. Submissions are reviewed by the Adom team before going live.

No sub-skills yet. Be the first to contribute one!

0 revisions · Updated 2026-04-16 10:56:12