Skip to content

feat(skills): move the container-nesting skill body to .agents/skills - #14

Merged
github-actions[bot] merged 1 commit into
mainfrom
feat/skills-to-agents-skills
Oct 20, 2026
Merged

github-actions[bot] merged 1 commit into
mainfrom
feat/skills-to-agents-skills

Conversation

@aitrawog

Copy link
Copy Markdown
Contributor

Summary

Moves the 1 container-nesting skill body out of the manifest and into the repo that owns them: .agents/skills/<name>/SKILL.md. Each skill: node keeps its routing metadata — name, family, owner, description — and no longer carries a body of its own.

The generator reads this shape already: plugin-marketplace/candy/plugin-marketplace/skillfile.go takes the body from the first declared tree that holds the file, defaults the id to the node name, and hard-errors when a node's inline content: disagrees with the file — and likewise when its description: disagrees with the file's frontmatter. That is what makes R5 mechanical rather than a convention.

Why the gate below is the generator and not charly box validate: box validate reports no skills at all, and measured on these repos it still returns rc=0 with a relocated SKILL.md deleted outright. It cannot see this change, so it is pasted for completeness and not as the proof.

How tested

Run on the committed tree at head f91c55a.

$ git rev-parse HEAD
f91c55a315f86c98806ad2a7e5c957c866e5b6b0

$ git diff --numstat origin/main...HEAD
482	0	.agents/skills/container-nesting/SKILL.md
0	466	charly.yml

$ git diff --stat origin/main...HEAD
 2 files changed, 482 insertions(+), 466 deletions(-)

$ git show origin/main:charly.yml | grep -oE '^        (name|family|owner|description|content):' | sort | uniq -c
      1         content:
      2         description:
      1         family:
      1         name:
      1         owner:

$ grep -oE '^        (name|family|owner|description|content):' charly.yml | sort | uniq -c
      2         description:
      1         family:
      1         name:
      1         owner:

That last block is why the routing metadata is intact rather than merely asserted: it counts each field in the
base and at the head, so name, family, owner and description are shown to survive unchanged while the skill bodies go. content goes from 1 to zero. A diff hunk's context is not evidence for this — it shows only the lines near the deletion.

Skill nodes at the head that still carry a body: 0 of 1 — which is the claim that
actually matters, and it is counted rather than asserted.

The generator's own reader, from a clone of plugin-marketplace at its released state, pointed at this tree:

$ SKILL_TREE=<repo> SKILL_NAMES=container-nesting go test -run TestTree -v -count=1 .
=== RUN   TestTree_RejectsMismatchedDescription
    desc_gate_test.go:20: SKILL_TREE/SKILL_ONE unset
--- SKIP: TestTree_RejectsMismatchedDescription (0.00s)
=== RUN   TestTree_ReadsFromAgentsSkills
--- PASS: TestTree_ReadsFromAgentsSkills (0.00s)
=== RUN   TestTree_RejectsStaleInlineBody
    hermes_tree_gate_test.go:58: rejected as expected: skill "container-nesting": the node's inline `content:` and its file disagree — delete the inline body and keep the file
--- PASS: TestTree_RejectsStaleInlineBody (0.00s)
PASS
ok  	github.com/opencharly/plugin-marketplace/candy/plugin-marketplace	0.021s
rc=0

ReadsFromAgentsSkills asserts the body comes from the file and equals it byte-for-byte, and that the description comes from the file's frontmatter. RejectsStaleInlineBody asserts the inline-content: disagreement rule fires.

The description-equality rule is exercised separately, because the generic reader above passes an empty
description and therefore skips if s.Description != "" && s.Description != sf.Description. The test passes the
node's real description: and the log line states the comparison result, not merely byte counts — two strings
that differ only in newline-versus-space have equal lengths, so equal lengths alone prove nothing. No claim is made
here about the YAML scalar style the node happens to use; the equality is measured, not inferred from the style:

$ cd /tmp/pm/candy/plugin-marketplace && GOWORK=off GOFLAGS=-mod=mod TMPDIR=/tmp SKILL_TREE=/tmp/wt-container-nesting SKILL_ONE=container-nesting go test -run TestTree_RejectsMismatchedDescription -v -count=1 .
=== RUN   TestTree_RejectsMismatchedDescription
    desc_gate_test.go:45: node_desc == file_desc: true  (node=449 bytes, file=449 bytes)
    desc_gate_test.go:48: node_desc = "Rootless nested podman/buildah/skopeo recipe. Ships zero cap_add — works\nvia surgical `unmask=/proc/*` security_opt plus dual-location\ncontainers.conf/storage.conf/policy.json plus two canonical env vars\nplus subuid layout that fits inside the outer user namespace. Authoritative\nsource for the `mount_too_revealing()` kernel RCA.\nUse when working with nested containers, the container-nesting candy,\nor any \"rootless-in-rootless podman\" question."
    desc_gate_test.go:49: file_desc = "Rootless nested podman/buildah/skopeo recipe. Ships zero cap_add — works\nvia surgical `unmask=/proc/*` security_opt plus dual-location\ncontainers.conf/storage.conf/policy.json plus two canonical env vars\nplus subuid layout that fits inside the outer user namespace. Authoritative\nsource for the `mount_too_revealing()` kernel RCA.\nUse when working with nested containers, the container-nesting candy,\nor any \"rootless-in-rootless podman\" question."
    desc_gate_test.go:51: sha256(node)=d8c572fc625c83ffe64aeb073c00d716764ec835e93c1c673bda1f5d727cb71a sha256(file)=d8c572fc625c83ffe64aeb073c00d716764ec835e93c1c673bda1f5d727cb71a
--- PASS: TestTree_RejectsMismatchedDescription (0.00s)
PASS
ok  	github.com/opencharly/plugin-marketplace/candy/plugin-marketplace	0.021s
rc=0

Negative control — the same gate, red: the reader run against a copy of this tree with .agents/skills/container-nesting/SKILL.md deleted:

$ SKILL_TREE=<copy with one relocated file deleted> SKILL_NAMES=container-nesting go test -run TestTree_ReadsFromAgentsSkills -v -count=1 .
=== RUN   TestTree_ReadsFromAgentsSkills
    hermes_tree_gate_test.go:32: container-nesting: fillSkillFromFile: skill "container-nesting": no body — neither an inline `content:` nor a SKILL.md under [.agents/skills]
--- FAIL: TestTree_ReadsFromAgentsSkills (0.00s)
FAIL
FAIL	github.com/opencharly/plugin-marketplace/candy/plugin-marketplace	0.027s
FAIL
rc=1

Green on the tree being pushed, red when a relocated body goes missing.

The manifest gate, for completeness:

$ charly box validate
charly box validate: OK — checked 1 candy, 7 distros, 7 builders; 0 warnings, 0 errors
rc=0

AGENTS.md names every one of these skills, so its enumeration needed no change.

Runtime identity

The attribution footer uses the runtime's own names, verified rather than guessed:

$ dsh --version
0.2.1-rc.2

$ jq -r '.record.rows.modelSelection.val.lastUsed | "provider: \(.provider)\nmodel: \(.model)"' ~/.dsh/storages/session_projcache/sessions/$DSH_SESSION_ID.json
provider: ollama-cloud
model: deepseek-v4.1-flash

The model and provider are read from the harness's own record of this session (keyed by DSH_SESSION_ID),
not from a config file: record.rows.modelSelection.val.lastUsed is what the runtime wrote for the turns that
produced this change. dsh is the harness (DeepSeek Harness) and deepseek-v4.1-flash the model it reports —
the same identity that signs this repo's own validator run.

Rulebook compliance

  • R0: /charly-internals:git-workflow, /charly-internals:cutover-policy and /charly-internals:skills loaded before the first git action.
  • R1: no failure in this leg; the extractor and stripper are fail-closed and both reported the expected count before writing.
  • R2: removes duplication — the body existed as an entity field and now exists once, in the file the repo owns. The name and description on the node are the routing identity the generator matches the file against; the generator errors if description diverges, so the two cannot drift silently.
  • R3: no workaround; no retry, no suppression, no partial write.
  • R4: manifest and authored content only; no documented command changes.
  • R5: the deleted bodies are swept and proven equal — the generator returns the file's body and rejects any inline remainder.
  • R6: git status read first; worktree off fresh origin/main; no force-push, no bypass, no push to main.
  • R7: the proof fails without the change — the red control above — and the changed path (the reader) executed live on the committed tree.
  • R8: no live service involved.
  • R9: the binary is the CalVer-stamped worktree build.
  • R10: change class is config (manifest + authored skill content); the gate is the generator's reader on the committed tree, rc=0, with a red control, plus charly box validate rc=0 and zero warnings.

Change classification

Config + authored skill content: 1 .agents/skills/*/SKILL.md file added, 1 inline content: block removed from charly.yml. No runtime, no Go, no pin.

Filed issues

  • opencharly/charly#983 — charly box validate does not validate skill bodies: a deleted or empty .agents/skills/<name>/SKILL.md passes with rc=0. Filed from this leg's evidence. The generator gate above exists in addition to that filing, not instead of it.

Agent: charly

Assisted-by: DeepSeek Harness ollama-cloud deepseek-v4.1-flash (fully tested and validated)

The bodies lived only as inline content: blocks on the skill: nodes. They now live in the
repo that owns them, at .agents/skills/<name>/SKILL.md, and each node keeps its routing
metadata (name, family, owner, description) with no body of its own.

The plugin-marketplace generator reads this shape (skillfile.go): it takes the body from the
first declared tree that holds the file and hard-errors when an inline content: disagrees
with the file, so no stale second copy can survive.
@github-actions

Copy link
Copy Markdown

Review — PASS

Head SHA: f91c55a315f86c98806ad2a7e5c957c866e5b6b0 (branch: feat/skills-to-agents-skills)

Security screen: PASS — data/config move only; no secrets, network, supply-chain, or guardrail change; no embedded directives.
Checklist: All applicable rules PASS or NA. The charly.yml byte-count (466 deleted = content: | + 465 body lines) reconciles with the 482-line SKILL.md (16-line frontmatter + 466 body incl. leading blank), the pasted description-equality sha256 and grep counts are internally consistent with the visible diff, and the generator reader's green + deleted-file red control prove the changed artifact through its real consumer. The box validate skill-blindness is correctly filed (opencharly/charly#983) as non-blocking, and fully tested and validated is justified for a config class (where documentation reviewed is forbidden). Comment thread is empty; nothing to disposition.

Verdict: PASS


ollama-cloud/deepseek-v4.1-flash — action-review.

View action run

@github-actions
github-actions Bot enabled auto-merge (squash) October 20, 2026 00:10
@github-actions
github-actions Bot merged commit f8466cd into main Oct 20, 2026
1 check passed
charly-auto-merge Bot added a commit that referenced this pull request Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant