feat(web): a coding agent is offered its repository's agent skills, and a `/name` loads one

Merged
DA
7d ago
+949-3

What

PR A of the agent-skills rollout (Dean: "support agent skills in the composer… auto-detect skills from the repo… / shows a list"). This PR is the agent side. The desktop's / picker comes in follow-up PRs.

  • Where skills are found: server/hosted/repository-skills.ts reads a repository's skill folders from GitHub on the default branch, through the existing read path (readRepositoryFolder + readRepositoryFile, on a read token for that one repository). The two roots are .agents/skills/<name>/SKILL.md (Codex and the cross-harness standard; this repo's own skills live there) and .claude/skills/<name>/SKILL.md (Claude Code).
    • Only folders count. GitHub lists a symlinked .claude/skills/x → .agents/skills/x as a blob, so it dedupes naturally. When two roots have the same name, the first root wins.
    • The name comes from the folder (the same way eve names one from its path) and must match the spec's [a-z0-9-], ≤64. A skill with no description is skipped. Claude Code's user-invocable: false is read, for the picker to honour later.
    • Bounds: 64 skills, SKILL.md ≤ 64 KB, description ≤ 1024 chars, 8 reads in flight.
    • Frontmatter is read by a small reader (plain, quoted, and |/> block scalars) instead of adding a YAML dependency.
  • How a skill reaches the agent (eve-native): eve 0.74 has dynamic skills and a load_skill tool. It lists the skills to the model and loads one's body when the user names it, but it never scans the checkout.
    • coder/skills/repository.ts (session.started) hands eve the repository's skills via host.skills (coder-host/skills.ts).
    • Each body is prefixed with its folder in /workspace/repository, so references/… resolve in the checkout. It is passed as markdown only, so eve writes nothing to the sandbox.
    • coder/tools/load_skill.ts re-exports eve's tool, because the coder runs defaultTools: false.
    • The instructions add one line: a /name in the developer's message means call load_skill first.
    • The composer's token will literally be /name in the message text, so no wire change is needed. It works for a start task and for a steering message alike, because skills are resolved per session rather than per turn.
    • Side effect: like Claude Code and Codex, the agent sees the repo's skill list and may load one unprompted.
    • A GitHub failure at session start is logged and means no skills, never a failed start.
  • Relay: eve reports a skill load as its own load-skill action / load-skill-result, not a tool-call. The shared relay now records it as a load_skill call, which was otherwise refused (no_call). load_skill is registered in CODER_TOOL / the writer's tool set and in the context estimate's authored tools.

Tests

  • tests/repository-skills.test.ts, over a scripted GitHub (tests/support/skills-repository.ts):
    • both roots, frontmatter forms, first-root-wins, a link, a bad name, oversize, missing, undescribed
    • the 64-skill bound
    • the description bound
    • GitHub failing
  • tests/coder-host.test.ts:
    • host.skills offers the repo's skills with the checkout-path preamble, and none when the repo is unreached or GitHub can't be read
    • a load-skill action and its result land as a load_skill call (completed and failed). This fails on main's relay.
  • tests/coder-agent.test.ts: the instructions name /name → load_skill. The authored tools still equal the writer's set, load_skill included.
  • coder/evals/coder.eval.ts (scripted model, real Postgres): a session opened with /release-notes for this week
    • The scripted model calls load_skill only if eve's announcement lists the skill.
    • The answer holds the load_skill part with the skill's body.
    • The reply follows it.
    • I ran it locally against Postgres 16: passes.

Evidence

  • ./scripts/check.sh: exit 0.
  • pnpm --filter @luke/coder test:agent (with DATABASE_URL): passes.
  • Server-only, no Mac UI: ./scripts/verify.sh was not run (Linux VM), and CI builds nothing for the Mac.

Notes

  • Follow-ups: B, the desktop's planning.skills read (same reader behind GET /api/plans/{id}/skills) and an agent-tab row for a load_skill call (it renders as a generic call until then). C, the composer's / picker and token.

🤖 Generated with Claude Code


Open workspace in Conductor


Devin Review