This repo authors the Opik agent skills. The server serves them through the
read_skill tool and as MCP resources, and CI publishes the same bytes as the
pack that npx skills add comet-ml/opik-skills installs, and the Claude Code
plugin opik installs them from this repo. This doc answers how an agent gets
a skill, how the pack and the plugin are built, and how to add a skill.
plugins/opik/skills/ that holds a SKILL.md.
That folder is the Claude Code plugin folder, so it holds real files.
src/opik_mcp/skills is a symlink to it, so an editable install reads the
skills live; the wheel maps the folder in with force-include.
Its name is opik or starts with opik-, and matches the directory.opik is the SDK reference. Each opik-<verb> skill does one task. Rules for
the frontmatter description are in .claude/rules/skills.md._skills_root) and packed (DEFAULT_SRC). A skill
authored elsewhere is in neither, but can still ship; see Traps.evals/skills/<skill>/, outside the plugin folder,
and are run by hand (see its HARNESS.md). A file under the skills tree is
served and packed unless one rule, EXCLUDED_DIRS plus dotfiles, in
src/opik_mcp/skills_catalog.py, drops it; the pack builder imports it.
The plugin installs the folder as is, with no rule.read_skill(skill_name) returns one document. resolve accepts a skill name
(opik-instrument, meaning its SKILL.md), a path (opik/references/tracing-python.md)
or a URI (opik://skills/opik/SKILL.md). These three are advertised. It also
tolerates a sibling path as a SKILL.md cites it (../opik/references/integrations.md)
and a bare reference name: opik/tracing-python drops the references/ folder
and the .md suffix, and resolves to opik/references/tracing-python.md.
The answer is a header, then the file byte for byte:
[read_skill: opik path=SKILL.md bytes=<n> uri=opik://skills/opik/SKILL.md]
The header names the resolved file, whatever form the caller used. A
SKILL.md with references ends with a footer listing their paths.
An unknown skill raises UnknownSkillError (kind validation) listing every
skill; an unknown document lists that skill’s documents, in the caller’s form.
read_skill_tool_description renders the tool description: a routing line
per skill from SKILL_SUMMARIES, and the name and path forms; the URI form
is documented on the skill_name argument. It fits the host’s
description cut-off, so no host drops its tail
(test_the_description_arrives_whole). It names no reference file; an agent learns a reference’s path from the footer of the
SKILL.md that cites it, so adding a reference costs no session anything
until that skill is read (test_tool_description_names_no_reference_path,
test_every_skill_md_footer_lists_its_references).
read_skill also serves the cost intelligence
guide, which lives outside this tree (see
cost-intelligence); every bundled skill
and the skill resources are unchanged.Each served file is a resource at opik://skills/<skill>/<path>, with one
template. List and read results carry ttlMs (SKILLS_TTL_MS) and
cacheScope (SKILLS_CACHE_SCOPE, public) on the result and in each
content’s _meta. An unknown opik://skills/ URI is an error listing what exists.
make skills-pack builds dist/opik-skills/ with README.md and index.json.
It copies files byte for byte and validates each skill with skills_ref (the
spec’s reference implementation) before and after. The build fails on an empty
source, an invalid skill, or a SKILL.md path that dangles or escapes the pack.
In CI the skills-pack job runs make skills-verify and
make skills-verify-source. On a push to main, publish-skills-pack uploads
the verified artifact to the pre-release tag skills-pack, where
comet-ml/opik-skills pulls it. This repo holds no credentials for that repo.
plugins/opik/ is the plugin folder. It holds .claude-plugin/plugin.json,
.mcp.json, a README.md, skills/ and assets/icon.png, and nothing it
loads lives outside it, because Anthropic’s directory installs and scans only
that folder.
The directory listing reads displayName, description, keywords,
icon, privacyPolicyUrl and termsOfServiceUrl from plugin.json.
Claude Code ignores icon and the two URLs. The icon is a square PNG,
1024 px, of Opik-Icon-Primary from the Opik Design System in Figma. The
directory accepts only PNG or JPEG, 512 to 2048 px, and reads the icon
once, on the first save in the developer portal.
/plugin install opik --marketplace comet-ml/opik-mcp installs it from
.claude-plugin/marketplace.json. The skills load as opik:<skill>..mcp.json registers opik-cloud (type: http, OAuth, no key) at the
fixed URL https://www.comet.com/opik/api/v1/mcp. /mcp shows it as
plugin:opik:opik-cloud. A server the user added by hand at the same URL
wins, and Claude Code skips the plugin’s. A self-hosted user adds their own
server by hand and disables the plugin’s in /mcp.main. Anthropic’s directory tracks the
claude-plugin branch, which each release moves forward (see
release).plugin.json carries the version in version.txt. bump-version in
release.yaml moves both, and the version job in ci.yaml fails when
they differ.uvx opik@latest mcp configure.plugins/opik/skills/opik-<verb>/SKILL.md with last_updated and
source_commit under metadata:. tests/skills/test_spec_compliance.py
checks the name and the provenance.SKILL_SUMMARIES, or the skill is missing from the routing
list and test_every_bundled_skill_has_a_summary fails.test_tool_description_stays_within_a_sane_budget, SURFACE_BUDGET_BYTES
and INSTRUCTIONS_BUDGET_BYTES. Trim the summary, or raise the budget and
record why in the comment next to it (AGENTS.md).scripts/skills_trigger_eval.py (needs a judge API key, not in CI) and
make skills-verify-source.SKILLS_TTL_MS.read_skill(name) -> server.read_skill -> skills_catalog.run_read_skill
-> resolve -> read_skill_file (importlib.resources)
resources/* -> skills_resources handlers -> skills_catalog.resolve_uri
-> read_skill_file
make skills-pack -> scripts/build_skills_pack.py -> dist/opik-skills/
src/opik_mcp/skills_catalog.py, the only runtime reader of the tree.install_skill_resources in
src/opik_mcp/skills_resources.py. HTTP installs it in build_app, stdio in
_run_transport in src/opik_mcp/__main__.py.scripts/build_skills_pack.py and the skills-* targets in
Makefile. tests/hermetic/test_stdio_session.py checks the forms over stdio.skill_name has no enum because it accepts paths and URIs; an enum would
reject valid calls at the host’s schema check (#175).SKILL.md
footer lists the paths to the agent that is about to need one (OPIK-8496)... and absolute paths find nothing. read_skill_file rechecks the set.scripts/ folder ships without editing an
allow-list. Evals stay out of the skills tree because the pack installs with
--all and the plugin installs the whole folder, so fixtures would land on
every user’s machine (#176).content_digest leaves out pack_version and source_commit, so a merge
that changes no skill opens no pull request in the consumer (#163).opik- prefix exists because the installer overwrites a same-named
skill without asking (#171).${user_config.KEY} URL as unregistered, with a
warning at install, and claude.ai chat ignored the server. A self-hosted
user adds their own server by hand. An env var URL such as
${OPIK_MCP_URL:-https://…} works in Claude Code, but the directory accepts
only an https:// URL, a ${user_config.KEY} reference or "".
${user_config.KEY:-default} is not a fallback either: the validator
rejects it and the loader drops the server.uvx, because the directory holds every version of a
plugin that does for a reviewer (OPIK-8848).SKILL_SUMMARIES may differ from the frontmatter on purpose. No test ties
them; see the comment in tests/skills/test_catalog.py for why.install_skill_resources catches only a missing _mcp_server; other errors
while installing raise. A second install is a no-op.npx skills add run against this repo resolves .claude/skills/ and
.agents/skills/ first, so a skill committed there, or at any other path the
installer searches, ships instead of the authored ones. .gitignore,
.claude/hooks/protect_paths.py and deny rules block the usual ways in, but
not cp or a script. make skills-verify-source compares the installed
names with plugins/opik/skills/ and fails CI on any difference.src/opik_mcp/skills symlink and
adds each skill twice, which fails the build. The wheel exclude in
pyproject.toml skips it; tests/hermetic/test_wheel_contents.py builds
that way. The Docker build copies plugins/opik/skills for the same map.tests/skills/test_catalog.py.tests/conformance/test_skill_resources.py, tests/hermetic/test_stdio_session.py.tests/skills/test_pack_build.py. Each skill passes the
reference validator: tests/skills/test_spec_compliance.py.tests/repo/test_agent_hooks.py.make skills-verify, make skills-verify-source.tests/skills/test_catalog.py.claude plugin validate --strict: make plugin-validate.opik-cloud at the fixed hosted URL, with no opik_mcp_url option, so the directory can match it to the Opik connector; self-hosted users add their server by hand (OPIK-9161).opik-annotate added; the read_skill intro says “if not already loaded” once to fit its line (OPIK-8951).opik_mcp_url option, default the hosted server, so a self-hosted Comet can use the plugin (OPIK-8848).plugin.json, .mcp.json with the hosted server, and a README, so it can be listed in Anthropic’s directory (OPIK-8848).plugins/opik/skills and evals to evals/skills, so the Claude Code plugin folder holds the skills as real files (OPIK-8848)..claude/skills/ and .agents/skills/, which would ship to users (#202).evals/ excluded from the wheel so eval fixtures do not ship (#176).opik-compare (#198).read_skill, so a host without the pack can read them (#175).opik- prefix, so the installer cannot overwrite another skill (#171).comet-ml/opik-skills (#163).