Skip to content

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 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.

charly -C <marketplace-checkout> marketplace generate --root <marketplace-checkout> --out <marketplace-checkout>

  • The marketplace verb is an out-of-process command plugin: it enters the CLI grammar only inside a project whose charly.yml declares it on a discover: 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.

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.

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”.