-
Notifications
You must be signed in to change notification settings - Fork 6.7k
Description
Summary
Replace the monolithic AGENT_CONFIG dictionary and per-agent case/switch logic with self-bootstrapping agent packs. Each agent becomes a self-contained artifact with a declarative manifest (speckit-agent.yml) and a Python bootstrap module (bootstrap.py) implementing setup() and teardown(). The core CLI becomes a thin orchestrator that resolves packs and invokes their bootstrap.
The 25 official agent packs ship embedded in the pip wheel alongside the core template pack (#1711), providing zero-config offline functionality. A new agent catalog system -- inspired by the extension (#1707) and preset (#1708) catalog patterns but purpose-built for agent lifecycle semantics -- serves as an override layer. Organizations, teams, and users can publish customized or alternative agent packs that take priority over the embedded defaults. Community agents distribute exclusively via catalogs.
Because packs own both install and uninstall, agent switching becomes a first-class operation via specify agent switch <agent>.
Takes inspiration from #1707 (extension multi-catalog) and #1708 (pluggable presets), and builds directly on #1711 (embedded core pack + Python command generation).
Problem
Adding a new agent today requires changes to 10+ files across 5 languages:
| File | Language |
|---|---|
src/specify_cli/__init__.py |
Python (AGENT_CONFIG + --ai help text) |
README.md |
Markdown (supported agents table) |
AGENTS.md |
Markdown (integration guide) |
scripts/bash/update-agent-context.sh |
Bash (path variable + case block) |
scripts/powershell/update-agent-context.ps1 |
PowerShell (path variable + switch case) |
.github/workflows/scripts/create-release-packages.sh |
Bash (ALL_AGENTS + case statement) |
.github/workflows/scripts/create-github-release.sh |
Bash (release artifact) |
.devcontainer/devcontainer.json or post-create.sh |
JSON/Bash |
pyproject.toml |
TOML (version bump) |
CHANGELOG.md |
Markdown |
Consequences:
- High contributor friction -- community agent PRs (feat: add Aider CLI support and update related documentation #1611, feat: add support for PI coding agent #1596, feat: add support for IFlow CLI as an AI agent #1402, feat: add Trae AI agent support #1392, etc.) each touch 10+ files with high review burden. Some (feat: add support for PI coding agent #1596 Pi, feat: add Trae AI agent support #1392 Trae, feat: add support for IFlow CLI as an AI agent #1402 iFlow) were superseded by maintainer reimplementations rather than merged directly, illustrating the friction
- Cross-platform drift -- Bash and PowerShell agent lists have already diverged
- ~286 lines of duplicated case/switch -- Each agent enumerated 4x in bash + PowerShell scripts
- Release ZIP explosion -- 25 agents x 2 script types = 50 release packages
- Blocked on core releases -- Community agents can't ship without a maintainer merge + release cycle
- No agent discovery -- Users must read the README to find available agents
- No agent switching -- Switching from Claude to Copilot requires manual cleanup; the system doesn't know what files belong to which agent
- No org curation -- Enterprises can't restrict which agents their teams use
The shipped --ai generic --ai-commands-dir <path> (#1639) proved that agent behavior is fully parameterized -- directory, format, argument token. This issue makes that parameterization the default architecture.
Dependencies
- feat(cli): Embed core template pack in CLI package for air-gapped deployment #1711 -- Embedded core pack with Python-based
generate_commands()/core.render_command()(hard dependency -- agent packs embed alongside the core pack)
Inspirations (not direct dependencies)
- feat(extensions): support multiple active catalogs simultaneously #1707 -- Extension multi-catalog system (catalog stack pattern,
install_allowedsemantics, priority ordering). Note: feat(extensions): support multiple active catalogs simultaneously #1707 is extension-specific; the preset system (feat(presets): Pluggable preset system with catalog, resolver, and skills propagation #1708) independently reimplemented the same patterns rather than reusing shared code. No shared catalog library exists today. - feat(presets): Pluggable preset system with catalog, resolver, and skills propagation #1708 -- Pluggable preset system (manifest pattern, pack-as-artifact concept, catalog resolution)
Backward Compatibility: --ai Must Work Throughout Migration
specify init --ai claude must continue to work identically at every point during the agent catalog rollout. While the agent catalog system is being brought online, the existing AGENT_CONFIG-based resolution remains the active code path for --ai. The new pack-based resolution is opt-in, similar to how --offline is opt-in today. The migration is incremental:
- Before agent catalog ships:
--ai claudeuses the currentAGENT_CONFIG+ case/switch logic. No change. - During migration:
--ai claudestill usesAGENT_CONFIGby default. The new pack resolution is available via an opt-in flag (e.g.,--agent-catalogor similar). This allows testing and validation of the pack system without affecting existing users. - After full validation: The opt-in flag becomes the default.
AGENT_CONFIGis removed.--ai clauderesolves entirely through the pack resolution order.
At no point should a user experience a regression from specify init --ai <agent>. The --ai flag is the primary interface; the catalog system is the implementation change behind it.
Proposed Solution
Architectural Shift
Inversion of control: push bootstrapping logic from core's case/switch blocks outward into self-contained agent packs -- but keep the official packs embedded in the wheel for zero-config operation.
TODAY:
Release script (case/switch) --> 50 pre-baked ZIPs
Core CLI downloads ZIP --> expands into filesystem
Intelligence lives in: release script + core CLI
PROPOSED:
Official Agent Packs embedded in pip wheel (lowest priority)
Catalog packs override embedded defaults (higher priority)
User/project packs override everything (highest priority)
Core CLI resolves pack by priority --> invokes pack's bootstrap
Intelligence lives in: the agent pack itself
Agent Pack Structure
Each agent pack is a directory with a standard layout:
claude/
speckit-agent.yml # Manifest: identity, metadata, requirements
bootstrap.py # Bootstrap module: setup() + teardown()
content/ # Optional: agent-specific static files
CLAUDE.md
Manifest (speckit-agent.yml) -- declarative metadata for catalog discovery and compatibility:
schema_version: "1.0"
agent:
id: "claude"
name: "Claude Code"
version: "1.0.0"
description: "Anthropic's Claude Code CLI for AI-assisted development"
author: "github"
license: "MIT"
runtime:
requires_cli: true
install_url: "https://docs.anthropic.com/en/docs/claude-code/setup"
requires:
speckit_version: ">=0.1.0"
tags: ["cli", "anthropic", "claude"]Bootstrap module (bootstrap.py) -- per-agent intelligence:
class Claude(AgentBootstrap):
AGENT_DIR = ".claude"
COMMANDS_SUBDIR = "commands"
CONTEXT_FILE = "CLAUDE.md"
def setup(self, project_path, script_type, options):
commands_dir = project_path / self.AGENT_DIR / self.COMMANDS_SUBDIR
commands_dir.mkdir(parents=True, exist_ok=True)
for template in core.get_command_templates():
content = core.render_command(
template, script_type=script_type,
format="md", arg_token="$ARGUMENTS",
frontmatter={"description": template.description},
)
(commands_dir / template.filename).write_text(content)
core.copy_pack_content(self.pack_path / "content", project_path)
core.install_scripts(project_path, script_type)
core.install_templates(project_path)
def teardown(self, project_path):
core.remove_dir(project_path / self.AGENT_DIR)
core.remove_file(project_path / self.CONTEXT_FILE)Core Bootstrap API (speckit_agent_api)
Stable utilities that all agent packs import:
core.get_command_templates()-- shared command templatescore.render_command(template, script_type, format, arg_token, frontmatter)-- render to target format (Markdown, TOML, agent.md)core.install_scripts(project_path, script_type)-- copy scripts to.specify/scripts/core.install_templates(project_path)-- copy artifact templatescore.copy_pack_content(source, dest)-- copy agent-specific static filescore.get_template(name)-- resolve template via feat(cli): Embed core template pack in CLI package for air-gapped deployment #1711 resolution stack
Interaction with Extension Custom Commands
Today, the extension system's CommandRegistrar reads AGENT_CONFIG to determine each agent's directory, command format (Markdown/TOML/SKILL.md), and argument placeholder ($ARGUMENTS / {{args}}). When an extension is installed via specify extension add, CommandRegistrar.register_commands_for_all_agents() transforms the extension's universal Markdown commands into agent-specific formats and writes them to each detected agent's command directory.
This creates a hard coupling between extensions and agent metadata. When AGENT_CONFIG is removed in favor of agent packs, the extension system must still be able to query agent metadata. Agent packs must expose the same information that CommandRegistrar currently reads from AGENT_CONFIG:
- Command directory: Where to write commands (e.g.,
.claude/commands/,.windsurf/workflows/) - Command format: Markdown, TOML, or skills-based
- Argument placeholder:
$ARGUMENTSor{{args}} - Skills directory overrides: For agents like Codex (
.agents/skills/) and Kimi (.kimi/skills/) - Special naming conventions: Copilot's
.agent.md/.prompt.md, Codex'sspeckit-{cmd}/SKILL.md
Design requirement: Each agent pack's manifest (speckit-agent.yml) or bootstrap class must declare enough metadata for CommandRegistrar to render extension commands without importing the bootstrap module. This keeps the extension system decoupled from agent lifecycle logic:
# speckit-agent.yml additions for extension command support
command_registration:
commands_dir: ".claude/commands" # Where extension commands are written
format: "markdown" # markdown | toml | skill
arg_placeholder: "$ARGUMENTS" # Argument token for this agent
file_extension: ".md" # Output file extension
# Optional overrides for skills-based agents:
# skills_dir: ".agents/skills"
# skill_name_pattern: "speckit-{cmd}"Impact on agent switching: When specify agent switch removes the old agent's directory (which contains extension-registered commands), those commands are lost. After the new agent's setup() runs, the switch operation must re-register all installed extension commands for the new agent using the extension registry (.specify/extensions/.registry) and CommandRegistrar. This is automatic — no user intervention required.
Impact on CommandRegistrar: The registrar currently reads from the AGENT_CONFIG dictionary. It must be updated to read agent metadata from the resolved pack's manifest instead. The AGENT_CONFIGS lookup table inside CommandRegistrar should be built dynamically from available packs rather than hardcoded.
Catalog Code Sharing
The agent catalog system should take inspiration from the extension (#1707) and preset (#1708) catalog patterns -- catalog stack resolution, priority ordering, download/caching, install_allowed enforcement, and JSON schema validation are common concerns. However, no shared catalog library exists today: the preset system independently reimplemented these patterns from the extension system rather than extracting shared code.
The agent catalog should follow the same approach: implement its own catalog infrastructure informed by the established patterns, but purpose-built for agent semantics. Agents operate at a fundamentally different level than extensions or presets -- they are installed and uninstalled, have setup/teardown lifecycles, support switching between agents in an existing project, and require CLI tool checks. These lifecycle differences mean the Extension and Preset classes cannot be reused directly.
A future refactoring could extract a shared catalog library across all three systems (extensions, presets, agents), but that is not a prerequisite for shipping the agent catalog.
Embedded Agents with Catalog Override
The 25 official agent packs ship inside the pip wheel, following the same pattern as the core template pack (#1711):
src/specify_cli/
core_pack/
templates/... # (from #1711)
commands/... # (from #1711) -- shared command templates
scripts/... # (from #1711)
agents/ # Official agent packs -- embedded in wheel
claude/
speckit-agent.yml
bootstrap.py
content/
gemini/
speckit-agent.yml
bootstrap.py
content/
copilot/
...
# ... all 25 official agents
This means pip install specify-cli gives you a fully functional toolkit -- every official agent works immediately, no network required, no catalog fetch on first use.
Resolution Order
When specify init --ai <agent> or specify agent switch <agent> is invoked, packs resolve by priority -- embedded agents are the lowest priority fallback:
Priority 1 (highest): ~/.specify/agents/<id>/ <-- User-level custom/override packs
Priority 2: .specify/agents/<id>/ <-- Project-level custom/override packs
Priority 3: Installed from catalog <-- From `specify agent add` or prior catalog downloads
Priority 4 (lowest): Embedded in wheel <-- Official packs bundled in pip package
Why this order matters:
- Default experience is zero-config.
pip install specify-cli && specify init --ai claudeworks offline, immediately. The embedded Claude pack bootstraps the project. - Catalogs are the override layer. An org publishes a customized "claude" pack to their internal catalog with adjusted conventions. When a developer runs
specify agent add claude(pulling from the org catalog), that version takes Priority 3 and shadows the embedded default. - Project-level packs win over catalogs. A specific repo needs a one-off agent tweak -- drop a pack in
.specify/agents/claude/and it shadows everything below it. - User-level packs win over everything. A developer's personal preference always takes priority.
Community agents (not embedded) distribute exclusively via catalogs. When specify init --ai aider is invoked and no local/cached pack exists, the CLI fetches it from the active agent catalogs. If the catalog is unreachable and no cached version exists, init fails with a clear message:
Agent 'aider' not found locally or in any active catalog.
Run 'specify agent search' to browse available agents, or
'specify agent add aider --from <path>' for offline install.
Override semantics: When a higher-priority pack has the same agent.id as a lower-priority one, the higher-priority pack is used entirely -- no merging, no partial override. This keeps resolution deterministic and debuggable. specify agent info claude shows which pack is active and why:
Agent: Claude Code (claude)
Source: catalog (org) -- ~/.specify/agent-cache/claude/
Overrides: embedded v1.0.0
Version: 1.2.0
Development and Testing Workflow
The resolution order makes agent development fast and rebuild-free:
Testing changes to an official agent:
# 1. Copy the embedded pack to a project-level override
specify agent export claude --to .specify/agents/claude/
# 2. Edit bootstrap.py, content files, manifest -- any changes
$EDITOR .specify/agents/claude/bootstrap.py
# 3. Re-run init or switch -- picks up local changes immediately, no rebuild
specify init --ai claude
# or
specify agent switch claude
# 4. When done, remove the override -- embedded version is back
rm -rf .specify/agents/claude/Developing a new community agent from scratch:
# 1. Create a pack directory anywhere
mkdir -p ~/dev/my-agent-pack
# Write speckit-agent.yml and bootstrap.py
# 2. Install from local path -- no catalog needed
specify agent add myagent --from ~/dev/my-agent-pack/
# 3. Test it
specify init --ai myagent
# 4. Iterate -- edit files, re-add, re-init. No wheel rebuild.
specify agent add myagent --from ~/dev/my-agent-pack/
specify agent switch myagentA/B testing agent variations:
# Drop a modified pack into the project
cp -r $(specify agent export claude --print-path) .specify/agents/claude/
# Edit .specify/agents/claude/bootstrap.py with experimental changes
specify agent switch claude # Uses the project-level override
# Compare output, then clean up
rm -rf .specify/agents/claude/
specify agent switch claude # Back to embedded defaultBecause higher-priority packs shadow lower ones without any registration step, the edit-test cycle is: save file, run command, see result. No pip install -e ., no wheel rebuild, no cache invalidation.
Agent Switching
specify agent switch <agent> invokes the current pack's teardown(), then the new pack's setup(), then re-registers extension commands.
Preserved: specs, plans, tasks, constitution, memory, templates, scripts, extensions, extension registry, git history.
Removed and recreated: agent directory (including extension commands within it), context file, agent-specific configs.
Extension command re-registration: After setup() creates the new agent's command directory, the switch operation must re-register all installed extension commands for the new agent. It reads the extension registry (.specify/extensions/.registry) to discover which extensions have commands, then invokes CommandRegistrar to transform each extension's universal commands into the new agent's format and write them to the new agent's directory. This ensures extensions survive agent switches without requiring manual specify extension add re-runs.
CLI Commands
specify agent search [query] [--tag TAG] # Search across active catalogs
specify agent add <id> # Download + cache from catalog (overrides embedded)
specify agent add --from <url-or-path> # Install from local source (overrides embedded)
specify agent list [--installed] # List available agents (embedded + catalog + local)
specify agent remove <id> # Remove cached/override pack (falls back to embedded if official)
specify agent info <id> # Show active pack, source, and override chain
specify agent export <id> --to <path> # Export active pack to a directory for editing
specify agent validate <path> # Validate pack structure (for contributors)
specify agent switch <id> # Switch active agent in existing project
Note on specify agent remove: Removing a cached catalog version of an official agent (e.g., specify agent remove claude) does not break anything -- the embedded version becomes active again. Removing a community agent that has no embedded fallback removes it entirely.
Community Contribution (After)
For official agent fixes/improvements: Submit a PR to the core repo -- the pack ships in the next wheel release.
For community agents (new agents not in the official 25):
1. Create speckit-agent.yml (5-7 lines of YAML)
2. Create bootstrap.py (~15-40 lines of Python)
3. Add optional content files
4. Submit catalog entry (PR to catalog.community.json -- 1 JSON block)
No core code changes. No cross-file coordination. No cross-platform script expertise required.
What This Resolves
Community Requests
| Request | Signal | Resolution |
|---|---|---|
| Multi-agent support (#1065) | 32 reactions | specify agent switch with setup/teardown |
| 8 community agent PRs | 8 PRs | Resubmit as self-contained packs |
| Agent switching (#1372, #1395, #1463) | 7+ reactions | First-class switch command |
| Niche agent requests (7 low-signal) | <3 reactions each | Zero-cost for maintainers; community-solvable |
| Offline agent provisioning (#1513, #1499) | -- | Works out of the box for official agents; specify agent add --from <path> for community agents |
| Agent discovery gap | -- | specify agent search/list |
Dev Roadmap Items Subsumed
| Item | Status |
|---|---|
| Phase 1: Manifest + scaffolding | Fully subsumed |
| Phase 2: Universal release packages | Fully subsumed |
| Phase 3: Remove legacy case/switch | Fully subsumed |
| Phase 5: Local agent workflow + switching | Fully subsumed |
| Phase 9: Custom agents | Fully subsumed |
| Phase 10: Agent contribution workflow | Fully subsumed |
| can-wait #1-3: Manifest, universal release, case/switch | Fully subsumed |
| nice-to-have #4: Multi-agent (32 reactions) | Resolved |
Combined with #1711 (phases 6-8), this chain resolves 10 of 12 planned phases.
Codebase Impact
| Area | Change |
|---|---|
src/specify_cli/__init__.py |
AGENT_CONFIG (~90 lines) removed; per-agent branching in init() replaced by resolve_agent_pack() + bootstrap.setup() |
src/specify_cli/core_pack/agents/ (new) |
25 official agent packs embedded in wheel |
src/specify_cli/agent_api.py (new) |
AgentBootstrap base class + core utilities (~100 lines) |
update-agent-context.sh |
~80 lines of case/switch --> ~15 lines of pack-driven loop |
update-agent-context.ps1 |
~70 lines of switch --> ~15 lines of pack-driven loop |
create-release-packages.sh |
ALL_AGENTS array + ~90-line case statement eliminated; release builds the wheel with embedded packs |
agents/catalog.json (new) |
Official agent catalog (for version-ahead overrides) |
agents/catalog.community.json (new) |
Community agent catalog |
| Release artifacts | 50 ZIPs --> 0 agent ZIPs (official agents in wheel; community agents via catalogs) |
Embedded vs. Catalog-First Trade-offs
| Concern | Embedded (this design) | Catalog-first (alternative) |
|---|---|---|
| Offline / air-gapped | Works out of the box for all 25 official agents | Requires pre-provisioning via specify agent add |
| First-run experience | pip install && specify init --ai claude -- instant |
pip install && specify init --ai claude -- network fetch on first use |
| Pip package size | Larger (25 packs in wheel) | Smaller (no agents bundled) |
| Agent update independence | Official agents update with CLI releases | Agents update independently via catalogs |
| Override path | Catalog/local packs shadow embedded defaults | N/A -- catalogs are the only source |
| Dev/test cycle | Edit local override, re-run command -- no rebuild | Same (catalog-installed packs are also local) |
What This Does NOT Address
- Spec editing/refinement (Spec-Driven Editing Flow: Can't Easily Update or Refine Existing Specs #1191, ~99 reactions)
- Constitution-aware review (Add /speckit.review as a final, constitution-aware quality gate #1323, 28 reactions)
- Custom branch naming ([Feature request] Custom namespacing and branch naming conventions #1382, 15 reactions)
- tinySpec lightweight workflow (Proposal: speckit.tinySpec: a lightweight workflow for small tasks #1174, 20 reactions)
- Template testing automation
- MCP implementation
Acceptance Criteria
-
speckit-agent.ymlmanifest schema defined and validated -
AgentBootstrapbase class withsetup()/teardown()contract implemented -
speckit_agent_api.coreutility module implemented (render_command,install_scripts,install_templates,copy_pack_content) - All 25 official agents restructured as self-contained packs under
src/specify_cli/core_pack/agents/ - Official agent packs included in pip wheel via
pyproject.tomlpackage data - Each pack's
setup()produces identical output to the current release script for that agent - Each pack's
teardown()cleanly removes agent-specific files while preserving shared infrastructure -
AGENT_CONFIGdictionary removed from__init__.py - Pack resolution order implemented: user-level > project-level > catalog-installed > embedded (lowest priority)
-
specify init --ai <agent>resolves pack by priority, invokesbootstrap.setup()-- works offline for all 25 official agents -
specify init --ai <community-agent>fetches from catalog when no local version exists -
specify agent switch <agent>tears down current agent and sets up new one, preserving specs/plans/tasks/constitution/memory -
specify agent search,add,list,remove,info,export,validateCLI commands implemented -
specify agent infoshows active pack source and override chain -
specify agent exportcopies active pack to a target directory for editing -
specify agent removeon an official agent falls back to embedded version - Agent catalogs (
agents/catalog.json,agents/catalog.community.json) created with multi-catalog schema - Agent catalogs implement catalog stack resolution with
install_allowedenforcement (following patterns established by extension and preset catalogs) - Catalog-installed packs override embedded packs of the same
agent.id -
update-agent-context.shcase/switch replaced with pack-driven queries -
update-agent-context.ps1switch blocks replaced with pack-driven queries -
create-release-packages.shagent case statement eliminated -- wheel contains embedded packs - User-level (
~/.specify/agents/) and project-level (.specify/agents/) custom packs supported -
--ai generic --ai-commands-dir <path>backward compatibility preserved -
specify init --ai <agent>works identically at every stage of migration -- no user-facing regression - During migration, pack-based resolution is opt-in (flag-gated);
AGENT_CONFIGremains the default until pack system is fully validated - Air-gapped install for community agents:
specify agent add <id> --from <path> - Edit-test cycle works without wheel rebuild: save local override, run command, see result
- AGENTS.md rewritten as Agent Pack Development Guide
-
CommandRegistrarupdated to read agent metadata from pack manifests instead ofAGENT_CONFIG - Agent pack manifests (
speckit-agent.yml) include command registration metadata (directory, format, arg placeholder, file extension) sufficient forCommandRegistrarto render extension commands -
specify agent switchre-registers all installed extension commands for the new agent aftersetup()completes - Extension commands survive agent switching without requiring manual
specify extension addre-runs - Skills-based agent packs (Codex, Kimi) correctly declare skills directory and naming conventions for extension command registration
- Unit tests: manifest validation, bootstrap API contract, resolution order (including embedded fallback), catalog override semantics, CLI commands, switch flow, extension command re-registration, output equivalence for all 25 agents