Files
agent-lib/README.md
T
m3ta-chiron 875a3c8aae feat(discovery): frontmatter guard for flat agents/commands docs
A flat .md under agents/ or commands/ whose frontmatter contains none of
the definition keys (name, description, mode, agent, model,
argument-hint, temperature, permission) is documentation, not an item:
discovery marks it Skipped with a recorded warning — it never enters a
selection, inventory, availability listing or collision index, but the
warning lands in the lockfile and add/update/select reports like other
discovery warnings. Skills keep their folder+SKILL.md marker, MCP keeps
the .yaml marker. README gains the canonical layout table and the
migration note for folder-style agent repos.

Closes beads: agent-lib-2n7
2026-08-23 15:17:46 +02:00

13 KiB

agent-lib

One Go binary, two roles: curator and client.

  • Curator sideagent-lib vendor … pulls external git repositories into a plain work repository under external/<source>/<type>/, pinned by revision, with per-source selection and collision-safe naming. Publication is nothing more than git commit && git push.
  • Client sideagent-lib sync pulls the work repository and deploys its content into the OpenCode runtime paths. Local modifications win over company updates, self-created items are never touched, and Windows toast notifications tell employees when their edits blocked an update.

No Nix, no Python, no git subprocess at runtime — a single static binary for windows/amd64, linux/amd64, linux/arm64, darwin/amd64 and darwin/arm64.

Installation

Download the binary for your platform from the GitHub releases, verify the checksum, and put it on your PATH:

curl -fsSLO https://github.com/m3tam3re/agent-lib/releases/latest/download/agent-lib_linux_amd64.tar.gz
tar xzf agent-lib_linux_amd64.tar.gz
sha256sum -c checksums.txt --ignore-missing
install -m 0755 agent-lib /usr/local/bin/
agent-lib version

Fleet-managed machines receive the binary via a pinned rollout — see docs/fleet-handoff.md. There is no self-update.

From source: go build . (version is baked in via ldflags; make build uses the git description).

Curator guide

The work repository layout:

skills/<id>/SKILL.md      own skills (folder artifacts)
commands/<id>.md          own OpenCode commands
agents/<id>.md            own OpenCode agents
mcp/<id>.yaml             own MCP fragments (never deployed by the binary)
external/<source>/<type>/ vendored content, mirrors the same four types
agent-lib.lock.json       lockfile v2 — pins every source

This layout is exactly what OpenCode natively consumes — agent-lib is deliberately on the opencode standard, no conversion layer:

Type Canonical form
skills <id>/SKILL.md folder (any depth, category nesting ok)
commands flat commands/<id>.md
agents flat agents/<id>.md
mcp flat mcp/<id>.yaml (inventoried, never deployed)

Migrating from folder-style agent repos (e.g. agents/<id>/AGENT.md layouts): flatten agents/<id>/AGENT.mdagents/<id>.md, and move documentation and manifests (SCHEMA.md, agents.json, README.md) out of the typed directories. No folder support for agents/commands is planned — adapting the source repo to the standard beats shipping a renderer.

Vendoring a source

agent-lib vendor add superpowers https://github.com/obra/superpowers/tree/main/skills

Pasted GitHub/GitLab web-tree URLs are normalized automatically. A URL pointing directly at a type directory (e.g. …/tree/main/skills) selects that directory as the skills root. --ref <branch-or-tag> pins a ref; the resolved revision is pinned in the lockfile. Discovery understands the four standard directories (skills/, commands/, agents/, mcp/); skills are folders containing SKILL.md — found at any depth, so category-nested layouts (skills/engineering/tdd/SKILL.md) work too, with the folder's base name as the skill id — commands and agents are flat .md files, MCP fragments are .yaml. A flat .md under commands/ or agents/ whose frontmatter contains none of the definition keys (name, description, mode, agent, model, argument-hint, temperature, permission) is treated as dropped documentation: skipped with a recorded warning, never an inventory entry. Everything is read-only scanned — upstream code is never executed.

Selection rules

  • Default: vendor everything (mode: all).
  • --include a,b,c switches the source to include mode: only those ids arrive, and new upstream items do not arrive automatically.
  • Hard errors, enforced by vendor add, vendor update, vendor select and validate:
    • all combined with an include list → error
    • an exclude list without all → error (not supported in v2 selections)
    • an include entry that no longer exists upstream → error naming the item

(vendor select changes selections later — see Changing your selection later below.)

Changing your selection later

vendor select manages the selection after the initial add — without ever advancing the pinned revision (that is vendor update's job):

agent-lib vendor select superpowers --add tdd,brainstorm   # include mode: grow
agent-lib vendor select superpowers --remove review,debug  # include mode: shrink
agent-lib vendor select superpowers --remove review        # all mode: move to exclude
agent-lib vendor select superpowers --mode all             # reset: take everything again
  • include mode: --add appends ids (validated against the pinned upstream — typos abort with the list of available ids), --remove drops ids and deletes their folders from external/.
  • all mode: --remove moves ids onto the exclude list (lockfile v2 supports exclude in all mode) and deletes their folders; --add is a hard error — everything is already selected.
  • --mode all clears include/exclude and re-materializes the full pinned upstream, reporting what appeared.

Every select re-runs the collision checks and honors the lockfile rename map — you can hand-add a rename for a not-yet-selected item, then --add it under its deployed name. Failed selects mutate nothing.

Collisions and renames

Every item deploys into a flat per-type namespace. If a vendored id collides with one of your own items (or with another source's item), the operation aborts with a hard error naming both parties — before anything is written. Resolve it with an explicit, reviewable rename:

agent-lib vendor add superpowers "$URL" --rename review=superpowers-review

The on-disk folder always carries exactly the deployed name. Renaming onto another collision is an error.

The maintenance loop

agent-lib vendor diff superpowers   # read-only preview: new/changed/deleted vs selection
agent-lib vendor update superpowers # fetch latest of the pinned ref, rewrite, report
agent-lib vendor update --all       # every source, reported per source
agent-lib vendor remove superpowers # delete external area + lockfile entry

In all mode new upstream items arrive automatically (and appear in the update report); in include mode they deliberately do not — vendor diff surfaces them as available upstream, not selected. Every update re-runs the collision checks and honors the rename map.

Auditing

agent-lib validate                 # offline: rules + tree/lockfile consistency
agent-lib vendor list              # sources with pin, mode, item counts
agent-lib vendor inspect <name>    # full inventory with metadata + warnings

All three accept --json. The lockfile is deterministic JSON (sorted keys, fixed indentation) — identical reruns produce identical bytes, so git diffs stay clean.

Client guide

Employees run two commands:

agent-lib sync     # pull the work repository and deploy
agent-lib status   # report local state; exit 1 when updates are blocked

Both accept --repo <url> / --ref <ref> for config-free personal use — see Configuration below.

Deployment mapping (OpenCode target):

Type Destination
skills ~/.agents/skills/<id>/ (whole artifact folders)
commands ~/.config/opencode/commands/<id>.md
agents ~/.config/opencode/agents/<id>.md
mcp never deployed — stays under fleet-controller merge

Sync semantics (three-state)

Sync plans before it mutates. For every item the planner compares the repository, the local manifest and the on-disk content:

  • new item → deploy
  • managed & unmodified, upstream changed → update
  • managed & locally modified → skip + warn — your version stays
  • on disk but never deployed by us → leave + warn — self-created items are safe by construction
  • upstream deleted & unmodified → remove
  • upstream deleted & modified → skip + warn — your version stays

Sync is idempotent (a second run is a byte-level no-op) and fails cleanly: an unreachable repository never leaves a half-deployed machine.

Configuration

The repository to pull comes from the first source that provides one:

  1. --repo <url> flag (works with no config file at all)
  2. --config <path> flag
  3. AGENT_LIB_CONFIG env
  4. user-level config (personal machines)
  5. admin-protected platform default (fleet rollout)
Source Linux / macOS Windows
user-level config $XDG_CONFIG_HOME/agent-lib/config.json (default ~/.config/agent-lib/config.json) %AppData%\agent-lib\config.json
admin default /etc/agent-lib/config.json %ProgramData%\agent-lib\config.json

--repo overrides a repo_url from any discovered config; --ref narrows it to a branch or tag. When no config exists anywhere, the error lists every searched location, a minimal working example, and the --repo shortcut.

{
  "repo_url": "https://git.example.com/company/agent-content.git",
  "ref": "main",
  "token": "<read-only deploy token>",
  "token_user": "oauth2"
}

The token is used for basic auth only. It never appears in the lockfile, the manifest, logs or error messages. On --repo runs (or when the discovered config carries no token) AGENT_LIB_GIT_TOKEN / AGENT_LIB_GIT_USER supply credentials instead — env never overrides a configured token.

Personal quickstart (no fleet, no config):

agent-lib sync --repo https://github.com/you/your-agent-content.git

The work repository must be a committed git repository (sync pulls it read-only via go-git; local uncommitted state is ignored).

Notifications

On Windows, a toast fires when (and only when) a sync kept local modifications or discovered unmanaged items — never for routine syncs. The same warnings are appended to the sync log (~/.local/state/agent-lib/sync.log on Linux, %LocalAppData%\agent-lib on Windows) and surfaced by status. Linux/macOS notification is a no-op.

status --json is machine-readable: deployed revision, repository revision, pending changes, skipped and unrecognized items, and a problematic_drift flag that drives the non-zero exit code — suitable for headless drift checks.

Security / trust model

  • External sources are untrusted. Discovery and vendoring never execute upstream code; only file contents are read.
  • The shared read-only token limits blast radius; rotation is a single fleet run (see the handoff doc).
  • MCP fragments from vendored sources are never activated by the binary; merging and vault substitution remain an explicit fleet-controller decision.
  • Binary updates happen exclusively via pinned fleet rollouts — there is no self-update path to abuse.

Troubleshooting

Symptom Cause / fix
error: … no client config found. Searched (in order): … No config anywhere. Create one where the error suggests, or skip it: agent-lib sync --repo <url>.
error: … include entry "x" not found upstream Typo, or the item was renamed/removed upstream. Fix the lockfile include list, or re-add with a corrected --include.
error: … collides with own skills "x" Deployed-name collision. Add --rename <upstream>=<deployed> (add) or fix the rename map (update).
validate reports tree/lockfile divergence Someone edited external/ by hand. Re-run vendor update <source> to restore the pinned state.
Skill didn't update, skipped (local modifications kept): 1 Working as designed — you edited the deployed item. Restore the company version by deleting the local copy and re-syncing.
status exits 1 Local modifications block repository updates (or occupy names the repo needs). See status output for the item list.
Sync fails with clone/fetch error Work repository unreachable, or token expired. Previous local state remains intact; fix config/network and re-run.
No toast on Windows Toasts need an interactive user session — the scheduled task must run as the logged-in user, not SYSTEM.

Development

make build   # build with version ldflags
make test    # unit + black-box e2e (fully offline, local fixture git repos)
make lint    # go vet + gofmt check
./scripts/e2e-acceptance.sh   # full spec walk: 60+ assertions, offline

Release: goreleaser release on a tagged commit builds all five targets with checksums and publishes to GitHub releases. Verify with goreleaser release --snapshot --clean or a tagged --skip=publish dry run.

Scope and non-goals (v2)

In scope: vendoring with selection/renames into any git work repository; OpenCode-only deployment with three-state protection; toast notifications; goreleaser/GitHub release pipeline.

Not in scope: client-side selection or profiles, additional targets (Pi, Hermes, Crush, Amp), MCP merge/vault (fleet-controller concern), binary self-update, git submodule sources, migration tooling from the Python-era lockfile.