marketplace
Recipe card from the charly-internals plugin (Development — contributor internals).
marketplace — how the skill corpus is generated, refreshed, and consumed
Section titled “marketplace — how the skill corpus is generated, refreshed, and consumed”The corpus is GENERATED; the refs list is the ONE input
Section titled “The corpus is GENERATED; the refs list is the ONE input”The opencharly/marketplace repo’s root (family dirs: internals/, build/, check/, core/, vm/,
automation/, kubernetes/, local/, image/, pod/, …) is the GENERATED corpus. Every SKILL.md
and every agent definition under <family>/agents/ carries a
GENERATED by charly marketplace generate — DO NOT EDIT banner: both are projections of an
owning repo’s file, so neither is edited in the corpus.
candy/charly-marketplace/charly.yml is the refs list: @github.com/opencharly/<candy-repo>:v<CalVer>
pins for every standalone candy repo that owns skill:/hook:/marketplace: entities. It
is the ONE generation input — change a skill at its SOURCE, never the generated copy.
The ownership split (post cutover)
Section titled “The ownership split (post cutover)”The charly repo owns NO skill: entities (its candy/ dir was emptied by the de-submodule cutover). A FAMILY is not the unit of ownership: the drains moved each skill to the repo that holds the verb it documents, so one family spans several repos, and the split moves with every such move — so the map is not restated here.
The map is the refs list. candy/charly-marketplace/charly.yml is the ONE generation
input: it names the sources that own skill:/hook:/marketplace: entities, and a source
it does not name reaches the corpus only through the closure of a ref that does. A published
body is the byte-identical copy of ONE source’s body — the text after the frontmatter of
that source’s .agents/skills/<name>/SKILL.md file, or the inline body of its manifest’s
skill: node. The generator writes that body with the ONE trailing newline the EOF
convention prescribes — and an authored file already ends with one — so the source’s body
and the published body (the one that follows the generated header) agree byte for byte. So
the owner of a published skill is the source whose body matches the published one
byte for byte: read the refs list for the sources, then match.
A skill edit lands in the OWNING repo as a FILE — <owning repo>/.agents/skills/<name>/SKILL.md,
or <owning repo>/.agents/agents/<name>.md for a sub-agent definition — while the skill: node
beside it carries only routing (family/owner/triggers). Then the marketplace refs list is bumped
and the corpus regenerated. Never hand-edit a generated SKILL.md or a marketplace family dir. The
authoring contract — the file’s shape, the routing-only node, the prohibition on an image owner:, and
the hard error on an inline body that disagrees with its file — is /charly-internals:skills; it is
not restated here.
Regeneration
Section titled “Regeneration”charly -C <marketplace-checkout> marketplace generate --root <marketplace-checkout> --out <marketplace-checkout>
- The
marketplaceverb is an out-of-process command plugin: it enters the CLI grammar only inside a project whose charly.yml declares it on adiscover:path (the marketplace repo’s own charly.yml exists for exactly this). - The charly binary must support the marketplace repo’s config schema (2026.240.1943-era
as of this writing); a binary built from a recent charly checkout works, an old one
fails with
config schema ... is newer than this charly supports. - Regeneration on a clean tree is a NO-OP for the committed corpus (hand-authored files sit outside the known paths); a diff means a refs/source change is in flight.
The drift gate (the marketplace repo’s scripts/drift-gate.sh — ONE implementation, TWO callers)
Section titled “The drift gate (the marketplace repo’s scripts/drift-gate.sh — ONE implementation, TWO callers)”The marketplace repo’s deploy workflow builds the pinned charly, regenerates the ENTIRE
corpus, and FAILS if the committed corpus does not match. The check lives in ONE script,
the marketplace repo’s scripts/drift-gate.sh, and BOTH of that repo’s workflows run it:
deploy.yml on every PR, and the daily refresh on the tree its own PR would commit.
The gate asks “did the tree move since the index was set”, in two halves: the WORKING
TREE against the INDEX (git diff --quiet), and the files in NO index at all
(git ls-files --others --exclude-standard) — the second half is what a diff alone
misses, because a generator that emits NEW artifacts creates them as untracked files. It
deliberately does NOT use git status --porcelain: that command reports the index column
as well as the worktree column, and the refresh stages its regenerated corpus on purpose,
so a gate built on it fails on the refresh job’s tree shape, every time it runs there.
Measured 2026-10-29 on the refresh’s own tree: a check built on it aborted with DRIFT
while the tree had not moved at all (no unstaged change, no untracked file). That is a
property of the INSTRUMENT on that tree shape — the refresh ran NO gate before
2026-10-29 (it pasted the gate’s SOURCE into its body), so no refresh ever aborted on
it.
A refs bump must therefore ship its regenerated corpus in the same PR, or the gate trips on the next run.
The daily refresh (refresh.yml)
Section titled “The daily refresh (refresh.yml)”Daily 04:41 UTC (+ manual dispatch): scripts/refresh-refs.sh advances every @github pin
in the refs list to its repo’s newest tag, regenerates, and opens a chore: refresh the refs list PR; it skips if one is already open. The content is NOT unreviewed — each
pinned repo validated its own change before tagging; the refresh republishes landed work
(it exists because hand-written CalVer pins go stale silently and the drift gate stays
green regardless).
Per-harness vendoring — the corpus is a CACHE, not a live source
Section titled “Per-harness vendoring — the corpus is a CACHE, not a live source”The Codex config reference
documents skills.max_context_tokens as the available-skills catalog budget: it
defaults to 2% of the model context window, and explicit values are capped at 10,000
tokens. Set [skills] max_context_tokens = 10000 for this large catalog. The catalog
can still exceed that cap, so use the AGENTS.md Skill Dispatcher to select each required skill,
then read its SKILL.md by path when it is absent from the session listing.
Claude Code clones the marketplace as charly-plugins; Codex registers it with
codex plugin marketplace add opencharly/marketplace and installs entries with
codex plugin add <plugin-name>@charly-plugins; kimi installs it as one plugin; pi installs it as a git package
(.pi/pi-claude-marketplace/ + state.json with commit-SHA versioning). Updating the
marketplace repo is NOT updating an installed plugin: each harness caches per committed
state, so a landed fix stays invisible until the harness refreshes its marketplace and
re-installs the plugin at scope.
Cross-References
Section titled “Cross-References”/charly-internals:skills— authoring: edit the owning repo’s.agents/skills/<name>/SKILL.md, then regenerate/charly-internals:git-workflow— PR-only landing + CalVer tags for the owning repos/charly-build:docs— the docs mirror of the same corpus (standalone opencharly/docs repo)/charly-build:charly-mcp-cmd— out-of-process plugin verbs and the discover:-path grammar
When to Use This Skill
Section titled “When to Use This Skill”Invoke before editing any skill source, bumping a refs-list pin, running charly marketplace generate, reviewing a refresh/drift failure, or answering “why is my
skill/agent stale in my harness”.

