Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 37 additions & 9 deletions .agents/skills/plugin-nerdctl/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
---
name: plugin-nerdctl
description: The out-of-tree charly ENGINE plugin serving engine:nerdctl (verb:nerdctl
/ command:nerdctl) — every engine op delegates to container.InvokeEngineOp. Use
when working with the engine:nerdctl provider, the plugin-nerdctl candy, or the
engine provider-class placement model.
description: The out-of-tree charly ENGINE plugin serving engine:containerd (verb:containerd
/ command:containerd) — every engine op delegates to container.InvokeEngineOp. `containerd`
is the engine word; `nerdctl` is only the CLI binary it is driven through. Use when working
with the engine:containerd provider, the plugin-nerdctl candy, or the engine provider-class
placement model.
---

# plugin-nerdctl

The out-of-tree charly ENGINE plugin serving `engine:nerdctl` (plus the
`verb:nerdctl` / `command:nerdctl` convenience words).
The out-of-tree charly ENGINE plugin serving `engine:containerd` (plus the
`verb:containerd` / `command:containerd` convenience words).

`containerd` is the ENGINE WORD. `nerdctl` is only the CLI BINARY that drives it —
the CUE-owned `#EngineBinaries` map (`containerd → nerdctl`) is the one home for
that mapping, and the retired engine spelling is gone (R5, no alias).

## What it is

Expand All @@ -23,11 +28,34 @@ registry.

Every engine op (`describe` / `binary` / `gpu_args` / `start_plan` /
`unit_emit` / `network_ensure`) is answered by delegating to the pure body
`container.InvokeEngineOp("nerdctl", …)` in the spec module — the SAME body the
compiled-in `engine:podman` / `engine:docker` providers serve. So the engine
`container.InvokeEngineOp("containerd", …)` in the spec module — the SAME body
the compiled-in `engine:podman` / `engine:docker` providers serve. So the engine
class has one implementation and two placements.

## The containerd start-plan policy

The one provider-specific policy is applied to `start_plan`:

- **A runtime handler is ALWAYS named.** nerdctl's unqualified default handler is
`runc` while the CRI default is `crun`, so an unqualified plan would silently
select a different handler; the plugin fills an empty `runtime` with `crun`
(validated against the capability's `runtime_handlers`) so `--runtime` is
always emitted.
- **Rootful is not silently rootless.** A rootful plan gets the capability's
`privilege_prefix` (e.g. `sudo -n`) as an argv prefix, taken from data.
`unit_emit` is a pure renderer and its envelope carries no scope field: it
renders a user unit from the `start_argv` it is given. A rootful unit carries
the prefix and `--runtime` only when the caller passes the rootful start-plan
argv; the provider does not re-apply the policy at the unit boundary.
- **Defaults are not re-spelled.** No `--address` / `--namespace` is emitted for
the capability defaults; a caller's override rides `extra_args`.

The capability row itself (`supports_rootless` / `supports_rootful`, the
mode-split workload users, `address`, `namespace`, `privilege_prefix`,
`substrates`, `runtime_handlers`) is served by the spec module's engine body —
one table, no copy here.

## Use when

Working with the `engine:nerdctl` provider, the plugin-nerdctl candy, or the
Working with the `engine:containerd` provider, the plugin-nerdctl candy, or the
engine provider-class placement model.
42 changes: 29 additions & 13 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# AGENTS.md — plugin-nerdctl

Standalone out-of-tree plugin repo serving the `engine:nerdctl` engine backend
(`engine:nerdctl` + `verb:nerdctl` + `command:nerdctl`). The plugin is a Go
Standalone out-of-tree plugin repo serving the `engine:containerd` engine backend
(`engine:containerd` + `verb:containerd` + `command:containerd`). `containerd` is
the ENGINE WORD; `nerdctl` is only the CLI binary it is driven through (the
CUE-owned `#EngineBinaries` map), never an engine word. The plugin is a Go
module at `candy/plugin-nerdctl/` (module path
`github.com/opencharly/plugin-nerdctl/candy/plugin-nerdctl`); the root
`charly.yml` declares `discover: box` + `discover: candy` so the repo is a
Expand All @@ -12,18 +14,21 @@ Canonical files:
- `candy/plugin-nerdctl/charly.yml` — the `plugin-nerdctl:` candy entity
(`plugin:` block, `plan:` checks) and the embedded `plugin-nerdctl-skill:`
skill entity.
- `candy/plugin-nerdctl/` — the Go source: `plugin.go` (provider + meta),
`schema/plugin.cue`, `cmd/serve/main.go`.
- `candy/plugin-nerdctl/` — the Go source: `plugin.go` (provider + meta + the
containerd start-plan policy), `schema/plugin.cue`, `cmd/serve/main.go`.
- `.agents/skills/plugin-nerdctl/SKILL.md` — the skill body, the one canonical
base the corpus projects `/charly-internals:plugin-nerdctl` from. The candy's
`plugin-nerdctl-skill:` entity is the owner registration paired with that body.
- `README.md` — user overview only; never agent guidance.

The repo carries **no** per-repo workflow file; the org-wide required workflow is
what gates the merge.
The repo carries only the `tag-on-merge` caller (`.github/workflows/`); the
org-wide required workflow is what gates the merge.

## Load these skills first (R0)

- `/charly-internals:plugin-nerdctl` — the `engine:nerdctl` provider reference
(projected from this candy's own `plugin-nerdctl-skill:` entity). Load before
changing an engine op.
- `/charly-internals:plugin-nerdctl` — the `engine:containerd` provider reference
(projected from this repo's `.agents/skills/plugin-nerdctl/SKILL.md`). Load
before changing an engine op.
- `/charly-internals:plugin` — the plugin authoring reference: the `plugin:`
block, the unified Provider model, the per-plugin CUE-schema contract,
placement.
Expand All @@ -34,7 +39,8 @@ what gates the merge.
## Build / validate / test

- `go build ./...` in `candy/plugin-nerdctl/` — compile the plugin module.
- `go test ./...` in `candy/plugin-nerdctl/` — the plugin's Go tests.
- `go test ./...` in `candy/plugin-nerdctl/` — the plugin's Go tests, including
the out-of-process go-plugin round trip (skipped with `-short`).
- `charly box validate` at the repo root — the structural check (the candy +
`plugin:` block, CUE schema).
- The merge gate is the **org-wide** `charly/pr-validator` (required check
Expand All @@ -45,11 +51,21 @@ what gates the merge.

- Edit the `plugin-nerdctl:` candy entity, the Go source, and `schema/plugin.cue`
**together** — the schema is the served declaration surface.
- Keep the delegation to `container.InvokeEngineOp("nerdctl", …)` in the spec
- Keep the delegation to `container.InvokeEngineOp("containerd", …)` in the spec
module: the engine class has ONE implementation served from either placement.
Do not fork the engine body here.
- Keep the `plugin-nerdctl-skill:` entity in step with any provider-surface
change — it is the projected source for `/charly-internals:plugin-nerdctl`.
- The PROVIDER-SPECIFIC policy belongs in the provider: `start_plan` always names
a runtime handler (nerdctl's unqualified default is runc, the CRI default
crun), and a rootful plan is prefixed with the capability's
`privilege_prefix` — never a hard-coded escalation, never silently rootless.
- Keep `nerdctl` out of the engine vocabulary. It is the CLI binary named by
`spec.EngineBinaries["containerd"]`; a second engine spelling is a cutover
regression.
- Keep `.agents/skills/plugin-nerdctl/SKILL.md` in step with any provider-surface
change: it is the skill BODY, the one canonical base the corpus reader resolves.
The candy's `plugin-nerdctl-skill:` entity is the owner registration the reader
pairs with that body (name/family/owner only — it carries no body); update its
metadata only when the name/family/owner changes.

## Landing

Expand Down
48 changes: 33 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
# plugin-nerdctl

The out-of-tree charly ENGINE plugin serving `engine:nerdctl` — the containerd
engine backend for OpenCharly pods, plus the `verb:nerdctl` / `command:nerdctl`
convenience words.
The out-of-tree charly ENGINE plugin serving `engine:containerd` — the containerd
engine backend for OpenCharly pods, plus the `verb:containerd` /
`command:containerd` convenience words.

`containerd` is the ENGINE WORD. `nerdctl` is only the CLI BINARY that drives it:
the CUE-owned `#EngineBinaries` map (`containerd → nerdctl`) is the one home for
that mapping, and the retired engine spelling is gone (R5, no alias).

The plugin is a standalone Go module: charly's loader host-builds `./cmd/serve`
and connects it **out-of-process** over go-plugin gRPC via the plugin SDK. It
answers every engine op by delegating to the pure body
`container.InvokeEngineOp("nerdctl", …)` in the spec module — the SAME body the
`container.InvokeEngineOp("containerd", …)` in the spec module — the SAME body the
compiled-in `engine:podman` / `engine:docker` providers serve — so the engine
class has one implementation and two placements.

## What it provides

| Capability | Surface |
|---|---|
| `engine:nerdctl` | the engine provider-class op envelope: `describe` / `binary` / `gpu_args` / `start_plan` / `unit_emit` / `network_ensure` |
| `verb:nerdctl` | the `nerdctl:` convenience verb |
| `command:nerdctl` | the `charly nerdctl` convenience command |
| `engine:containerd` | the engine provider-class op envelope: `describe` / `binary` / `gpu_args` / `start_plan` / `unit_emit` / `network_ensure` |
| `verb:containerd` | the `containerd:` convenience verb |
| `command:containerd` | the `charly containerd` convenience command |

The start plan always names an OCI runtime handler (nerdctl's unqualified default
is `runc`, the CRI default `crun`), and a rootful plan is prefixed with the
capability's `privilege_prefix` (e.g. `sudo -n`) instead of being treated as
rootless. The capability defaults for `address` / `namespace` are never
re-spelled; an override rides `extra_args`.

`unit_emit` is a pure renderer and its envelope carries no scope field: a
rootful unit is a user unit whose `ExecStart` is the (prefixed,
`--runtime`-bearing) start-plan argv the caller supplies. This provider does not
re-apply the policy at the unit boundary.

Composes with the `layer-nerdctl` candy (the packages + rootless
containerd/buildkit/CNI stack) to deploy a pod with `engine: nerdctl`.
containerd/buildkit/CNI stack) to deploy a pod with `engine: containerd`.

## How to use it

Expand All @@ -34,20 +49,23 @@ Compose the plugin candy alongside the layer candy in a box's `candy:` list:
Then select the engine on the deploy:

```yaml
engine: nerdctl
engine: containerd
```

## Layout

- `candy/plugin-nerdctl/` — the plugin module: `plugin.go` (provider + meta),
`schema/plugin.cue`, and `cmd/serve/main.go`.
- `candy/plugin-nerdctl/` — the plugin module: `plugin.go` (provider + meta +
the containerd start-plan policy), `schema/plugin.cue`, and
`cmd/serve/main.go`.
- `.agents/skills/plugin-nerdctl/SKILL.md` — the skill body, the one canonical
base the corpus projects `/charly-internals:plugin-nerdctl` from. The candy's
`plugin-nerdctl-skill:` entity is the owner registration paired with that body
(name/family/owner only); it carries no body.
- `charly.yml` — the root project manifest (`discover: box` + `discover: candy`).
- The candy's `plugin-nerdctl-skill:` entity is the projected source for the
`/charly-internals:plugin-nerdctl` skill.

## Related

- Owning skill: `/charly-internals:plugin-nerdctl` — the engine:nerdctl provider
reference (projected from this candy's own `skill:` entity).
- Owning skill: `/charly-internals:plugin-nerdctl` — the engine:containerd
provider reference (projected from this repo's skill body).
- `/charly-internals:plugin` — the plugin/provider model and placement.
- [`opencharly/charly`](https://github.com/opencharly/charly) — the charly CLI.
53 changes: 35 additions & 18 deletions candy/plugin-nerdctl/charly.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,58 @@
# plugin-nerdctl — the out-of-tree charly ENGINE plugin serving `engine:nerdctl`.
# plugin-nerdctl — the out-of-tree charly ENGINE plugin serving `engine:containerd`.
#
# A standalone Go module served over go-plugin gRPC via the charly plugin SDK; the
# charly loader host-builds ./cmd/serve and connects it OUT-OF-PROCESS. It answers
# every engine op by delegating to the pure body container.InvokeEngineOp in the
# spec module — the SAME body the compiled-in engine:podman/engine:docker providers
# serve — so the engine class has one implementation and two placements.
#
# `containerd` is the ENGINE WORD. `nerdctl` is only the CLI binary that drives it
# (#EngineBinaries maps containerd -> nerdctl); the retired engine spelling is gone
# (R5, no alias).
plugin-nerdctl:
candy:
description: |
The out-of-tree charly ENGINE plugin serving engine:nerdctl (plus the
verb:nerdctl / command:nerdctl convenience words). It answers the engine
provider-class op envelope (describe / binary / gpu_args / start_plan /
unit_emit / network_ensure) by delegating to the pure body
container.InvokeEngineOp("nerdctl", …) in the spec module, so the engine
class has one implementation served from either placement. A standalone Go
module (go.mod + cmd/serve) host-built and connected over go-plugin gRPC.
Composes with the layer-nerdctl candy (the packages + rootless
containerd/buildkit/CNI stack) to deploy a pod with engine: nerdctl.
The out-of-tree charly ENGINE plugin serving engine:containerd (plus the
verb:containerd / command:containerd convenience words). containerd is the
engine word; nerdctl is the CLI binary named by the CUE-owned #EngineBinaries
map, never a literal here. It answers the engine provider-class op envelope
(describe / binary / gpu_args / start_plan / unit_emit / network_ensure) by
delegating to the pure body container.InvokeEngineOp("containerd", …) in the
spec module, so the engine class has one implementation served from either
placement. Its start plan always names an OCI runtime handler (nerdctl's
unqualified default is runc, the CRI default crun) and prefixes a rootful
plan with the capability's privilege_prefix (e.g. sudo -n), so a rootful
request is never silently run as rootless; the capability defaults for
address and namespace are never re-spelled. The unit_emit op is a pure
renderer and its envelope carries no scope field: a rootful unit is a user
unit whose ExecStart is the (prefixed, --runtime-bearing) start-plan argv
the caller supplies, and this provider does not re-apply the policy at the
unit boundary. A standalone Go module
(go.mod + cmd/serve) host-built and connected over go-plugin gRPC. Composes
with the layer-nerdctl candy (the packages + rootless
containerd/buildkit/CNI stack) to deploy a pod with engine: containerd.
plugin:
providers:
- engine:nerdctl
- verb:nerdctl
- command:nerdctl
- engine:containerd
- verb:containerd
- command:containerd
source: github.com/opencharly/plugin-nerdctl/candy/plugin-nerdctl
plan:
- check: the out-of-tree engine:nerdctl plugin ships a buildable Go module (go.mod + cmd/serve) the host can build + serve out-of-process
id: nerdctl-module-present
- check: the out-of-tree engine:containerd plugin ships a buildable Go module (go.mod + cmd/serve) the host can build + serve out-of-process
id: containerd-module-present
context:
- build
command: "true"
- check: the plugin declares the engine:nerdctl provider word
id: nerdctl-provider-declared
- check: the plugin declares the engine:containerd provider word
id: containerd-provider-declared
context:
- build
command: grep -q 'engine:nerdctl' charly.yml
command: grep -q 'engine:containerd' charly.yml

# ---- HARNESS SKILL ----
# Owner registration for the skill body at .agents/skills/plugin-nerdctl/SKILL.md,
# the one canonical base the corpus reader resolves. This entity names the owner
# (name/family/owner) and carries no body; the body is the file at the repo root.
plugin-nerdctl-skill:
skill:
name: plugin-nerdctl
Expand Down
2 changes: 1 addition & 1 deletion candy/plugin-nerdctl/cmd/serve/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Command serve is the OUT-OF-PROCESS entrypoint for the nerdctl engine plugin: a
// Command serve is the OUT-OF-PROCESS entrypoint for the containerd engine plugin: a
// thin shim serving the importable provider over go-plugin gRPC via sdk.Serve. The
// SAME NewProvider()/NewMeta() compile INTO charly in-process when listed in
// compiled_plugins; this binary is host-built + connected only when they are NOT —
Expand Down
Loading
Loading