Agent-First Data v0.11: The Skill Installer, in Four Languages

by Agent-First Kit Contributors

A spore can ship its own Agent Skill — but getting that SKILL.md into Codex, Claude Code, and opencode is fiddly, per-agent filesystem work. v0.11 adds run_skill_admin: install, uninstall, and status for an embedded skill, with the same behavior and byte-identical output across Rust, Go, Python, and TypeScript.

A tool that an agent drives is more useful when it ships the instructions for driving it — an Agent Skill, a SKILL.md the coding agent loads and follows. But shipping the file is the easy half. Installing it is the fiddly half: each agent keeps its skills in a different place, the directory differs again between personal and project scope, and a careless write clobbers a skill the user hand-edited. Every spore that wanted to install its own skill was about to reimplement the same path-juggling, the same overwrite guard, the same “is this copy current?” check.

v0.11 makes that a library call instead. run_skill_admin installs, uninstalls, and reports status of a spore’s embedded skill across Codex, Claude Code, and opencode — and it does the same thing, the same way, in all four languages.

One call, three agents, two scopes

You describe the skill once and pick an action:

run_skill_admin(spec, "install", { agent: "all", scope: "personal" })

spec carries the skill name, the bundled SKILL.md, a title, and a short marker slug. agent is all or one of codex / claude-code / opencode; scope is personal or project. The library knows where each agent looks — ~/.codex/skills, ~/.claude/skills, ~/.config/opencode/skills for personal, the project’s .claude/skills and .opencode/skills for project — including the CODEX_HOME and XDG_CONFIG_HOME overrides. Codex has no project scope, so --agent all --scope project simply skips it rather than failing.

It won’t clobber what it didn’t write

Install stamps two marker comments into the file — a “generated by” line and a managed-skill marker keyed to your slug. Those markers are how the installer tells its own file apart from one a user wrote by hand. If a skill already exists at the target path and it carries neither the markers nor byte-identical bundled content, install and uninstall refuse to touch it unless you pass --force:

refusing to overwrite unmanaged skill at ~/.claude/skills/agent-first-widget/SKILL.md
  hint: pass --force to replace it, or choose another --skills-dir

The default is conservative on purpose. The installer manages what it created and leaves everything else alone.

status knows when a copy is stale

The interesting field in status is current. It is true only when the installed content matches the skill the binary ships right now — markers stripped, blank runs normalized, then compared. Upgrade the tool and the bundled skill changes; the copy on disk does not. current flips to false, and re-running install refreshes it. No version field to read, no manual diff: the content is the source of truth.

{"code":"skill_status","skill":"agent-first-widget","current_all":false,
 "targets":[{"agent":"opencode","installed":true,"valid":true,"managed":true,"current":false}]}

status reports installed, valid (front matter parses), managed, and current per target, plus the rollups — so an agent can look once and know whether to install, refresh, or do nothing.

A structured result, not a bag of JSON

run_skill_admin returns a typed report, not a pre-serialized blob. You read its fields directly, or you serialize it yourself — whichever the calling code needs. Each port uses the shape that is idiomatic for the language:

The library never writes to stdout or stderr; rendering stays the caller’s decision, which is the same contract the rest of AFDATA’s output layer keeps.

The same, in four languages

This is the part that took the work. The generated SKILL.md — markers, spacing, and all — is byte-identical across Rust, Go, Python, and TypeScript, and so is the serialized report. The front-matter validator, the managed-marker logic, and the normalize-then-compare freshness check are hand-ported rather than wrapped, so there is no dependency to drift and no per-language YAML quirk to reconcile. The test suites mirror each other case for case, and a cross-language diff pins the output.

It is filesystem and CLI tooling for the spore binaries, not part of the cross-language data convention — so in Rust it lives behind the skill-admin cargo feature, and ships as a normal module in the Go, Python, and TypeScript ports.

The agent rule

If your tool ships a skill, let the tool install it:

A spore can now carry its own instructions and put them where the agent will find them, the same way on every runtime it supports.