internals/skills/capabilities/SKILL.md
MUST be invoked before any work involving: OCI label contract, Capabilities / BoxMetadata struct, CapabilityLabelMap completeness check, LabelService structured round-trip, source-less deploy via `charly deploy from-box`, or adding a new OCI label. Developer-facing; users author via `/charly-image:layer` and `/charly-image:image`.
npx skillsauth add overthinkos/overthink-plugins capabilitiesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Every opencharly image carries a complete snapshot of "what can this image do, what does it need, what does it provide" as ai.opencharly.* OCI labels. This skill documents the contract, the single Go type behind it, the completeness test that keeps it honest, and the source-less deploy path it enables.
Source of truth: charly/capabilities.go + charly/labels.go. The CLI read-side probe is charly box labels <ref> (whole contract sorted; --format <key> for one raw value with a non-zero exit when absent; --all for non-charly labels too) — the charly-native R8 artifact check. See also /charly-internals:go for the broader Go architecture and /charly-internals:install-plan for how this feeds the build/deploy IR.
Capabilities type alias// charly/capabilities.go:29
type Capabilities = BoxMetadata
Capabilities is a Go type alias — not a separate struct. Every existing consumer that holds an *BoxMetadata (there are many) transparently participates in the capabilities contract. New code (K8s generator, charly deploy from-box) uses the canonical Capabilities name for readability. Aliasing (rather than wrapping) means there's exactly one place fields are defined: BoxMetadata in charly/labels.go.
Why this matters: adding a field anywhere in BoxMetadata automatically becomes part of the capabilities contract, which means it MUST have a CapabilityLabelMap entry. Forget the entry and CI blocks the PR.
CapabilityLabelMap — field → OCI-label namecharly/capabilities.go:35 names every label that participates in the contract. Entry grouping (identity / account / ports / security / networking / env / engine+init / distro+builder / hooks+vm / skills / data / dependency-graph / tests) mirrors the BoxMetadata field ordering so the map reads as a spec of the on-disk format.
Key entries:
| Field | Label const | What it stores |
|---|---|---|
| Version | LabelVersion (ai.opencharly.version) | The image's content-derived EffectiveVersion — its dedicated version: if set, else the highest layer version: across the chain (charly/effective_version.go). NOT the per-build tag; stable when no layer changed. Short-name resolution + charly clean retention prefer this label over the tag. Also the "is this an charly image?" presence sentinel (ExtractMetadata returns nil when empty). |
| Service | LabelService (ai.opencharly.service) | Structured JSON array of CapabilityService — not just names. 23 per-entry fields including kind, events, auto_start, start_retries, priority, init, layer. See "LabelService" below. |
| Init | LabelInit | Init system name (supervisord / systemd / none). |
| ServiceNames | LabelInit | Per-init active-name list; baked alongside LabelInit for CLI ergonomics (e.g., charly service status). |
| Tests | LabelEval | Three-section {candy, box, deploy} JSON — the tests baked into the image, consumed by charly eval live / charly eval box. See /charly-eval:eval. |
| Shell | LabelShell | Three-section {candy, box, deploy} JSON shell-init manifest. Each entry carries an Origin (candy name / "box" / "deploy"), an ID for overlay keying, an optional Generic body (intrinsic init + path_append) and a per-shell ByShell map (bash/zsh/fish/sh sub-blocks). Consumed by charly box inspect, charly deploy from-box, and MergeDeployShell for charly.yml shell: overlay merging. See /charly-image:layer "Shell Init Surface". |
| EnvProvide / MCPProvide | LabelEnvProvide / LabelMCPProvide | Cross-container discovery: what env vars / MCP servers this image advertises to pod peers. |
| EnvRequire / MCPRequire | LabelEnvRequire / LabelMCPRequire | What this image needs from peers — validated at charly config time. |
TestCapabilityLabelCompleteness — the guardrailcharly/capabilities_test.go:TestCapabilityLabelCompleteness runs on every go test ./... invocation. It uses reflect.TypeOf(BoxMetadata{}) to enumerate every exported field and fails if any field is missing from CapabilityLabelMap:
// charly/capabilities.go:143
func checkCapabilityLabelCompleteness() error {
rt := reflect.TypeOf(BoxMetadata{})
var missing []string
for i := 0; i < rt.NumField(); i++ {
name := rt.Field(i).Name
if _, ok := CapabilityLabelMap[name]; !ok {
missing = append(missing, name)
}
}
// ...
}
This is the enforcement mechanism that keeps the OCI-label contract and the Go struct in sync. Workflow for adding a capability:
BoxMetadata in charly/labels.go with a JSON tag.LabelFoo = "ai.opencharly.foo") next to the other label consts.CapabilityLabelMap entry: "Foo": LabelFoo.writeLabels (via writeJSONLabel) / ExtractMetadata.go test ./... passes.Skip step 3 and the test fails with BoxMetadata fields without CapabilityLabelMap entry: [Foo].
LabelService — structured per-entry service dataThe services label is a full structured round-trip — not a flat list of names:
// charly/labels.go (CapabilityService struct, paraphrased)
type CapabilityService struct {
Name string
Scope string // system / user
Enable bool
UsePackaged string // name of distro-shipped unit to reuse
Exec string
Env map[string]string
Restart string
WorkingDirectory string
User string
After []string
Before []string
Stdout string
StopTimeout string
Kind string // "program" (default) | "eventlistener"
Events string // required when Kind == "eventlistener"
AutoStart *bool // three-state; supervisord autostart=
StartRetries int
StartSec int
StopSignal string
ExitCode string
Priority int
Init string // which init owns it (supervisord / systemd)
Layer string // source layer name
}
Why this matters: charly deploy from-box (see below) reconstructs the full deploy surface from OCI labels alone. A names-only Service label would leave deploy-time K8s manifest generation blind to whether a process needs start_retries: 3 or is an eventlistener. The structured label carries every supervisord directive faithfully, and the K8s Kustomize generator (see /charly-kubernetes:kubernetes) reads from it without touching the source repo.
charly deploy from-boxCapabilitiesFromLabels(engine, imageRef) at charly/capabilities.go:166 is the source-less entry point: given an engine + image ref, it runs ExtractMetadata (which pulls labels via podman inspect / docker inspect), returns a fully-populated *Capabilities, and every downstream consumer (deploy target, K8s generator, quadlet generator) reads from that struct.
caps, err := CapabilitiesFromLabels("podman", "ghcr.io/overthinkos/fedora-coder:latest")
// caps.Service[0].Kind == "eventlistener" works — no source repo needed
This is what enables the "K8s deploy without access to charly.yml" invariant: a Kustomize overlay can be generated from a published image alone, for dev/staging/prod clusters that never see the build repo.
The long-term direction (documented in charly/capabilities.go:17-22) is to split BoxConfig into three discriminated sections:
box.build: — Containerfile inputs (base image, layers, distro/builder selection). Consumed only by charly box build.box.capabilities: — the runtime contract documented here. Emitted as OCI labels.box.deploy: — target-specific defaults (K8s storage class, container-target port defaults). Consumed by charly deploy add.Today these co-exist in a single BoxConfig. The Capabilities alias is the stepping stone — once the schema split lands, Capabilities will point at the capabilities: subsection directly instead of aliasing the whole struct. The CapabilityLabelMap completeness test will keep the contract honest across the migration.
See /charly-image:image for current user-facing structure and /charly-build:migrate for the one-shot charly migrate converter that emits the new schema when the split lands.
charly/labels.go (LabelFoo = "ai.opencharly.foo").BoxMetadata with a matching JSON tag.CapabilityLabelMap entry: "Foo": LabelFoo.writeLabels (label emission at build time, via writeJSONLabel for struct/list values).ExtractMetadata (label read-back at deploy time).writeJSONLabel for consistent encoding (see how LabelService, LabelEval, LabelVolume do it).go test ./... — TestCapabilityLabelCompleteness passes./charly-internals:capabilities (this skill) with the new entry in the key-labels table./charly-image:image — user-facing box: entries in charly.yml/charly-image:layer — user-facing candy: authoring, including service: which feeds LabelService/charly-core:deploy — charly deploy add / from-image / sync commands/charly-kubernetes:kubernetes — K8s deploy target that reads LabelService to generate Kustomize/charly-eval:eval — three-section LabelEval (layer/image/deploy) — same label-contract pattern/charly-build:migrate — charly migrate — emits the schema that populates these labels/charly-internals:go — Go architecture overview, LoadUnified, parseCandyYAML/charly-internals:install-plan — internal IR shared across build and deploy pipelinestools
OpenCharly CLI (charly) binary installed into container/VM images for in-container use. Use when working with charly binary deployment inside containers, native D-Bus support, or the full charly toolchain (charly binary + virtualization + gocryptfs + socat).
development
Operator CachyOS workstation profile — a kind:local template + target:local deploy that installs the full dev stack (30 candies) onto a CachyOS host via ShellExecutor. Lives in the overthinkos/cachyos submodule. MUST be invoked before editing or applying the charly-cachyos workstation profile.
tools
Fedora box with the full charly toolchain using shared candies. Rootless-first — runs as uid=1000 with passwordless sudo (no root, no cap_add: ALL). Same candy list as charly-arch. Includes NVIDIA GPU runtime. MUST be invoked before building, deploying, configuring, or troubleshooting the charly-fedora box.
tools
Arch Linux box with the full charly toolchain. Rootless-first — runs as uid=1000 with passwordless sudo (no root, no cap_add: ALL). Composes /charly-coder:charly-mcp so the box is reachable as an MCP gateway on port 18765. NVIDIA GPU runtime composed in. MUST be invoked before building, deploying, configuring, or troubleshooting the charly-arch box.