Files
magpie/docs/subsystems/library.md
T
yetone c4476d5658 library: skills from GitHub are checked as the skills page opens, and one changed here is marked and asked about before an update replaces it (#1449)
YangZhiqiang98 asked (#1449) that the Library find which skills have
updates and update them. magpie already checked skills from GitHub (a
market install among them) by hand and could update them, but an update
deleted the library's folder, so a skill the user had changed (by hand,
through an agent's link, or in an agent's copy not yet taken in) was lost
without a word, and nothing checked unless the user pressed Check.

- A skill is "changed here" when its library folder no longer hashes to
  what was fetched (Finder's .DS_Store aside), or an agent's copy was
  edited since its mark and not yet taken in (editState, skill_changed.go).
  The view says so (SkillView.edited) and the row shows "Changed here".
- UpdateSkill refuses such a skill with *EditedError unless told to
  replace it. The row's Update asks in the page first (Keep mine / Update;
  Update sends replace). Update N, the group's Update N and the CLI's
  update-all leave it as it is and say so. The CLI takes
  `magpie library skill update <name> --replace-edits`.
- The version an update replaces is moved to the backups when it was
  changed, or when it can't be told (a skill with no hash from before).
- View.checkDue: no check since magpie started, or none for 12 hours. The
  page then runs the check once by itself as the skills open, and says
  only what there is to update or add.

Not checked, by design: skills the skills CLI manages (`npx skills
update` updates them; magpie only groups them), linked local folders,
skills imported from an agent with no source, and From-only skills have
no remote; CC Switch-installed skills can still be updated by hand.

Verified:
- go test -tags nogui ./... (sandbox HOME); new TestUpdateKeepsASkillChangedHere,
  TestUpdateKeepsACopyEditedInAnAgent, TestUpdateKeepsWhatItCantTell,
  TestCheckDue, TestCheckSkillsMarksItDone, all from httptest. With
  editState forced to "not changed" the first three fail.
- gofmt -l, go vet, GOOS=linux and GOOS=windows go vet -tags nogui on
  ./internal/library ./internal/gui ./internal/tui .
- GUI: new skill-update-edited.test.cjs (Chromium and WebKit, 860 and
  400px, en and zh; no scroll on click; fails with the row's ask removed),
  plus the 22 other library skill tests, gui-ja, gui-de, gui-zh-tw,
  i18n-keys and click-scroll: 262 pass.
- Real use in a sandbox HOME with github.com/anthropics/skills: installed
  pdf and docx, the real check said both current; an edit through Claude
  Code's link was refused by `skill update pdf`, skipped by `skill update`
  (docx updated), replaced by `--replace-edits` with the edit in
  backups/<time>/library/skills/pdf; docx edited later shows edited, a
  .DS_Store doesn't.
2026-10-10 09:14:35 +08:00

16 KiB

Library: instructions, MCP servers, skills and RTK

The library keeps what every agent should know in one place: the instructions each agent reads before a conversation, the MCP servers it can call, and the skills it can load. magpie writes these into each agent's own files in that agent's format, and takes back only what it wrote. Everything else in those files stays as it was. Package internal/library also gives agents RTK, a CLI that cuts shell output down before the model reads it.

Responsibilities and sources of truth

Part Responsibility Source
library.json Library holds the instruction sets, the MCP servers, the skills, the projects, skill groups and copy settings. Applied records what magpie last wrote into each agent, so it only ever takes away its own. The instruction texts and skill folders live under Dir (library/ in magpie's config folder). library.go
One change change locks the library, loads it, applies one edit, saves, syncs every agent and saves again. Every exported mutation (SaveServer, InstallSkills, SetSkillHow and the rest) goes through it. One change runs at a time, whether from the page or the CLI. library.go
Targets Targets lists the detected agents (agent.Detected), plus apps like Claude Desktop, with where each keeps its instructions file (and an Override it reads instead), its MCP file and its skills folder. An agent in a running WSL distro has its places under the distro's home (wslTargetOf): Claude Code, Codex, Pi and OmO each by its own rule, and every agent that keeps all of them in its own folder (Hermes Agent, omp, OpenCode, Droid and the rest of ownFolder, #1323) by the same rule as on this machine, its MCP file marked as WSL's so a Windows program isn't given to it. targets.go
Sync sync writes each target's instructions and MCP servers. It then takes edits made in agents' skill copies into the library (takeEdits) and links or copies the skills. ~/.agents/skills goes first, so an agent that reads it gets no second link. Then it syncs projects' skills. sync.go
Instructions magpie's part of an agent's instructions file sits between two marker lines. The rest of the file is the user's. Several named sets can be switched between (#106). instructions.go
MCP servers A Server is a command or a URL (http, sse). mcpFile encodes it in each agent's own format (one mcpFormat per agent family) and merges it with the user's own fields for that entry. Also files get the same servers; Extra files are only read when importing. CheckServers connects as an agent would and keeps the result in memory only. A server that needs OAuth is signed in once in magpie, and agents reach it through the gateway's /mcp/<name> (#615). mcp.go, mcp_check.go, mcp_signin.go
Skills A skill is a folder with a SKILL.md. It comes from GitHub, from a folder on this machine (linked, so editing the folder edits the skill) or from the market. Each agent gets a link or a marked copy (#896, SkillHow). An agent in a WSL distro always gets a copy. A copy's mark keeps the hash of what was copied, so a sync knows a copy unchanged since then is current without reading its files (copyIsFresh, #1031); other copies are hashed. A skill an agent has of its own (foundSkills, the page's "In your agents") is brought in with ImportSkill, or taken out of every agent that has it with RemoveFoundSkill (#1303): its folders and copies of the very same files, its shared ~/.agents/skills entry and a folder of it in the library's own folder go to the backups, its links are taken away, and a folder elsewhere it only links to, or another skill by that name, stays. skills.go, skill_how.go, skill_edits.go, skill_from.go
Skills market MarketSkills lists skills.sh's most installed and what its search finds. magpie's own skills (featuredSkills) go first, marked official: magpie-quota, in magpie-community/plugins, lets an agent read magpie quota --json and wait with magpie quota wait. Until skills.sh lists one it has no count of installs and no skills.sh link (featured). InstallMarketSkill installs any of them from its repository. market.go
Skills a repository added A check for updates (CheckSkills) also asks, for each GitHub repository and ref the library has skills from, GitHub's list of its files (repoTree, one API request, free when the ETag still matches). Only when that list has a skill folder beside the installed ones (under newBase, their deepest common folder) that the library doesn't have and hasn't offered does findNew fetch the repository for the skills' names and descriptions. SeenSkills records per repo@ref the paths the install picker showed and the ones set aside, so they aren't offered again. The finds live in memory (newFound) and reach the page as View.NewSkills; AddNewSkills gives each to the agents that have its repository's other skills, and IgnoreNewSkills sets it aside (/api/library/skills/{add-new,ignore-new}). skillnew.go, skillcheck.go
Skill updates A skill from GitHub (kind: github, a market install among them) keeps the commit it was fetched at and Hash, the hash of its files as fetched. CheckSkills asks GitHub for each one's last commit and keeps the result in memory; View.CheckDue says none was checked since magpie started, or not for checkEvery (12 hours), and the page then checks by itself as the skills open (#1449). editState says a skill was changed here when the library's folder no longer hashes to Hash (.DS_Store aside), or an agent's copy was edited since its mark and not yet taken in; the view's edited says so. UpdateSkill refuses such a skill with *EditedError unless told to replace it (the page's own Update asks first; the CLI takes --replace-edits), and UpdateSkills/UpdateSomeSkills leave it as it is, in Unupdated. A replaced version that was changed, or that can't be told (no Hash), is moved to the backups (keepReplaced). Skills the skills CLI manages (npx skills, ~/.agents/.skill-lock.json) are only grouped: the CLI updates them (npx skills update). A linked folder, a skill imported from an agent and a From-only skill have no remote to check; one CC Switch installed (Origin) can be updated by hand but isn't checked. skill_changed.go, skillcheck.go, skills.go
Backups Before a change first writes an agent file, backups copies it to BackupDir, one folder per change. The last keepBackups (30) changes are kept. backup.go
RTK SetRTK runs rtk's own installer (rtk init -g …) for an agent being switched on. For one being switched off, it removes exactly what the installer wrote, using magpie's own code, which works even with rtk gone. ReadRTK reads each agent's files to say which have the hook. rtkSpec.blocked stops the installer where it would break an agent. A detected agent RTK has no hook for at all is still listed, from rtkNoHook, with the reason and a switch that can't be turned on (dsh: its hooks can allow or deny a command but not rewrite it, and rtk init has no --agent dsh). rtk.go

Runtime path

  1. Edit. The Library page or magpie library calls an exported function such as SaveServer, ServerAgents, InstallSkills, SkillAgents or SetSkillHow. The skills and servers pages each have Select, Turn all on, Turn all off and Remove all. Select's bar calls SomeSkillsAgents or SomeServersAgents (/api/library/{skills,servers}/agents-some), Turn all on or off calls EverySkillAgents or EveryServerAgents for the agents shown, and Remove all calls RemoveSkills or RemoveServers. Each of these is one change that takes every item out as a single removal would (#1027).
  2. Change. change edits library.json under the lock and calls sync.
  3. Write. For each target, syncInstructions and syncMCP write magpie's part in the agent's format and record it in Applied. Skills are linked or copied into the agent's skills folder. A copy edited since it was made is taken into the library first.
  4. Result. The Result lists the agents changed, a Problem for each thing that couldn't be given, and the backup folder.

Constraints and failure behavior

  • magpie takes away only what Applied or its mark says it wrote. A folder of the user's with a skill's name is never touched. The user's own fields in an MCP entry are kept.
  • An edit never silently loses. When an agent's copy of a skill and the library both changed, the newest edit wins, and the losing one is kept with the backups.
  • An update from GitHub never silently replaces a skill changed here. It is refused, or skipped in an update of several, until the user says to replace it, and the version replaced goes to the backups.
  • A failure for one agent is a Problem in the result. It doesn't stop the others.
  • A server an agent can't reach is not written there, and mcpFile.supports says why. For example, Claude Desktop takes no remote server, and Codex, Goose, dsh, Grok, Command Code and Zed take no SSE.
  • A server's headers (remote) or environment (command) can name a variable as ${NAME} (#1250). library.json keeps the placeholder, and refsOf gives each MCP format the agent's own syntax, with the agent's source cited beside it: ${NAME} for most, {env:NAME} for OpenCode and MiMo, ${env:NAME} for Cursor, and Codex's own keys (bearer_token_env_var, env_http_headers, env_vars). Goose gets ${NAME} plus env_keys. dsh (DeepSeek Harness) gets a !!js expression it evaluates as it loads the server (#1435): a whole ${NAME} is !!js process.env.NAME, and a value mixing text and variables is the text as JSON string literals joined with (process.env.NAME ?? ""), so the user's text is never run as code and an unset variable inside it is empty. A whole variable that isn't set makes dsh leave the server out as invalid. magpie reads only those forms back as ${NAME}; any other !!js keeps the entry the user's. An agent with no syntax (Claude Desktop, Antigravity, Kimi, Cline, ZCode, Zed, Devin, Alma, Pi with pi-mcp-extension) is never written the value: refsProblem makes supports refuse the server, a magpie entry already there is taken out, and AgentView.NoEnvRefs greys its chip with the reason. Codex and Goose's extra keys are magpie's only while the server names a variable there (refKeys), so the user's own env_vars or env_keys are kept. CheckServers expands the variable from magpie's own environment, and one that isn't set is novar, naming it.
  • magpie's own MCP server (magpie-image, and any mcp … run by a program named magpie) runs this computer's magpie (#1439). InstallServer gives it magpie by full path (update.Program: the AppImage, not its mount; Homebrew's opt link, not the Cellar), and that path is right only on this computer. When the library is read, and after every change, heal points such a server whose program isn't on this computer (another system's path, a magpie moved out of Downloads, a bare magpie) at this magpie, so the agents and CheckServers get it; a magpie program that is here (another build) and a server run by any other program stay as they are. A carried library (Collect, for a backup or a WebDAV or S3 sync) names it as plain magpie, so two computers carry the same bundle and a sync doesn't write one's path over the other's; Put then reads it as that computer's magpie. self_here.go
  • WorkBuddy's servers go in $WORKBUDDY_CONFIG_DIR/mcp.json (default ~/.workbuddy), and CodeBuddy Code's go in the first of .mcp.json, mcp.json and ~/.codebuddy.json that exists, as resolveMcpFilePath reads them (#1266). WorkBuddy connects a server only after the user has switched it on once in its own MCP settings. It keeps that approval in mcp-approvals.json, keyed by the command, args and env names or the URL's origin, so changing those asks again. magpie never writes the approval, and the servers page says so when a server is given to WorkBuddy.
  • Alma's servers go in ~/.config/alma/mcp.json on every system (its home, not its data folder), as its own MCP settings write them: command/args/env, or url/headers with transport: "sse" only for SSE (#1292). Alma reads the file as it starts and when its MCP settings refresh, so a server given to it while it runs reaches it after one of those.
  • Zed's servers go in context_servers of the settings.json magpie sets its model in (agent.zed: ~/.config/zed on macOS, $XDG_CONFIG_HOME/zed on Linux, %APPDATA%\Zed on Windows, or $MAGPIE_ZED_CONFIG_DIR), as command/args/env or url/headers (ContextServerSettingsContent, zed-industries/zed 2c99f547). The file is JSONC: it is edited in place, so the user's comments, other settings and own servers stay, and enabled, remote, timeout and oauth in magpie's entry are the user's. Zed speaks streamable HTTP only (its http.rs only POSTs), so no SSE. An entry Zed's extensions configure (settings and no command or url) is never written over: a server by that name is refused for Zed (mcpFile.foreign), and asks for another name. Zed reloads the file as it changes.
  • An agent with no place for an item is refused by Takes, never recorded as given it. The refusal lists the agents here that take that item (takenBy), and says where it goes instead (noPlace): OpenChamber reads OpenCode's global AGENTS.md, skills folder and opencode.json (its server's shared.js), so it gets them from OpenCode; Claude Desktop's chat keeps its instructions in its own settings, with the Claude account, and its Code tab reads Claude Code's.
  • Removing RTK never uses rtk init --uninstall. That command deletes Gemini's GEMINI.md whole and removes Claude Code, OpenCode and Cursor together.
  • CC Switch's own skills folder is only ever read.
  • Skills installed together (InstallSkills, InstallMarketSkill, AddNewSkills, through installFrom and installChange) install each on its own. One the library lists from the same source, or a folder in the library's own skills folder holding exactly that skill's files (left by an install that stopped before the library was saved), is had already and goes in Result.Had. Its files are never written, so the user's edits stay. Another skill by that name, listed or only in the folder, or a folder that can't be read, is left as it is and goes in Result.Skipped with why. The rest are installed and named in Result.Installed. Only when nothing was installed or had does the change fail, with the first reason.

Verification

go test -tags nogui ./internal/library -run 'TestCheckSkillsFindsNewOnes|TestNewBase|TestUpdateKeeps|TestCheckDue|TestCheckSkillsMarksItDone|TestTargets|TestServer|TestImportServer|TestCheck|TestInstructions|TestInstructionSets|TestSkill|TestRemoveFoundSkill|TestInstallSet|TestInstallLists|TestEverySkillAgents|TestEveryServerAgents|TestSomeServersAgents|TestRemoveServers|TestRTK|TestEnvRefs|TestDshRefRoundTrip|TestDshImportKeepsJS|TestAgentMCPFiles|TestCodeBuddyMCPFile|TestZedMCP|TestOwnServerFromElsewhereRunsThisMagpie|TestUsersOwnServerStaysAsItIs|TestCarriedOwnServerIsEachComputersMagpie'
go test -tags nogui ./internal/library
node --test internal/gui/tests/library-items.test.cjs internal/gui/tests/library-rtk-gain.test.cjs internal/gui/tests/library-rtk-nohook.test.cjs internal/gui/tests/library-servers-bulk.test.cjs internal/gui/tests/library-env-refs.test.cjs internal/gui/tests/library-zed-mcp.test.cjs internal/gui/tests/library-workbuddy-trust.test.cjs internal/gui/tests/skill-new-in-repo.test.cjs internal/gui/tests/skill-update-edited.test.cjs internal/gui/tests/remove-found-skill.test.cjs

The package's TestMain runs in a home of its own (testenv), never the user's real agent files.