Skip to content

Implementation Plan: 004 — Agent Registry

Feature Branch: feature/004-agent-registryDate: 2026-05-22 Spec: ./spec.mdStatus: Draft

Summary

Add the agent Resource kind to Coffer: a registry of locally-installed AI agents. Two types are wired in the capability manifest — Claude Code (claude_code) and OpenAI Codex (codex) — each covering both the CLI and the app/IDE form of the product, which share one config directory. Discovery is read-only: a scan reports installed-but-unregistered agents as candidates and the user confirms which to add — nothing is auto-registered (including on startup). Users can also add, edit, and remove agents manually.

On top of the registry, the feature adds two capabilities:

  1. Config-file read-only view + open externally — each agent type exposes a curated allowlist of its own config files (Claude Code: settings.json, settings.local.json, ~/.claude.json, CLAUDE.md; Codex: config.toml, AGENTS.md). The UI renders them read-only and offers open-in-external-editor / reveal-in-file-manager for each file and its containing folder (the path/folder_path pair). Programmatic save (REST/CLI) validates per format, writes atomically, and keeps a .bak. The same atomic-write + .bak machinery also backs the Coffer-MCP install/uninstall.
  2. One-click Coffer-MCP install — write/remove a coffer stdio MCP-server entry (pointing at coffer-mcp-shim) into the agent's MCP config, with status/idempotency.

The kind exposes an on_delete hook that the 005-skill-manager spec wires for skill-binding cleanup. Ships with REST routes, CLI subcommands, and a desktop Agents page.

This spec lays the second consumer of the kind-agnostic Resource framework introduced in spec 001, validating the framework's portability.

Technical Context

DimensionValue
Language / VersionPython 3.12+, TypeScript 5.x
New runtime dependenciestomlkit (MIT) — format-preserving TOML edit for Codex config.toml MCP install.
StorageSQLite at ~/.coffer/coffer.db. No new table — agents are rows in the generic resources table (head migration stays at 0004). Config files + MCP-install state are NOT persisted — agent config files on disk are the source of truth.
Testing4-tier (unit / integration / contract / e2e); acceptance markers tie to scenarios.
Target PlatformsmacOS arm64+x64, Windows x64, Linux x64+arm64
Performance GoalsDiscovery scan ≤ 200 ms cold. CRUD operations ≤ 50 ms each.
ConstraintsLocal-first 127.0.0.1 only; layered architecture preserved; no new credential storage.
Scale≤ 8 registered agents per user.

Constitution Check

ClauseComplianceNotes
I. Local-First (NON-NEGOTIABLE)Pure local registry; no network calls.
II. Spec-as-TruthThis plan implements spec.md; spec committed before code.
III. Open-Source-ReadinessOne new dep tomlkit — MIT, open-source; noted in PR description per governance.
LanguagesPython + TypeScript only.
Architecture: layeredNew code follows surfaces → application → domain → infrastructure. No domain → infra imports.
Persistence: SQLite for control planeRegistry in SQLite.
CredentialsNone.
Network defaultsLoopback-only HTTP. Discovery reads the local filesystem only.

Project Structure

Documentation

specs/004-agent-registry/
  spec.md
  plan.md              (this file)
  data-model.md
  contracts/api.openapi.yaml
  quickstart.md

New backend modules

backend/coffer/domain/agent/
  __init__.py
  types.py             # AgentType StrEnum (claude_code, codex) + default_config_dir + detect markers
  config.py            # AgentConfig (Pydantic)
  config_files.py      # ConfigFileFormat, ConfigFileSpec, config_files_for(), validate_content, spec_for
  mcp_install.py       # apply_install / apply_uninstall / is_installed (pure text transform, tomlkit for TOML)

backend/coffer/application/agent/
  __init__.py
  service.py             # AgentService (register [name optional] / update / remove)
  auto_detect.py         # AutoDetectService.discover() -> list[AgentCandidate] (read-only scan, returns candidates, no suppress list, not run on startup)
  config_file_service.py # AgentConfigFileService (list/read) + ConfigFileStorePort
  mcp_service.py         # AgentMcpService (status/install/uninstall) + ShimResolver
  kind.py                # make_agent_kind(on_delete_hook) -> Kind

backend/coffer/application/fs/
  __init__.py
  browse_service.py      # BrowseService.browse(path) -> immediate subdirs (read-only folder browse)

backend/coffer/infrastructure/agent/
  __init__.py
  config_file_store.py    # ConfigFileStore: read_text / stat; write_text_atomic (+ .bak) for config-file saves and the Coffer-MCP install

backend/coffer/surfaces/http/agent_routes.py         # GET/POST /agents, GET/PATCH/DELETE /agents/{name}, GET /agents/candidates
backend/coffer/surfaces/http/agent_config_routes.py  # GET /agents/{name}/config-files[/{key}], GET/POST/DELETE /agents/{name}/mcp-install
backend/coffer/surfaces/http/fs_routes.py            # GET /fs/browse (read-only folder browser backing the web folder picker)
backend/coffer/surfaces/cli/agent_cmd.py             # coffer agent {add, list, edit, rm, detect, config ls|cat, mcp status|install|uninstall}

New frontend modules

frontend/src/pages/AgentsPage.tsx                 # existing list page
frontend/src/components/agents/
  AgentAddForm.tsx / AgentEditForm.tsx / AgentTable.tsx   # existing
  FolderPicker.tsx         # config-dir folder picker (OS-native dialog on desktop; GET /fs/browse folder browser on web)
  AgentConfigPanel.tsx     # per-agent config-file list + read-only viewer (file list + read-only content view with format label and open-in-external-editor / reveal for the file and its folder)
  AgentMcpInstall.tsx      # one-click install/uninstall toggle + status badge
frontend/src/lib/api/agents.ts                     # extend with config-file + mcp-install calls
frontend/src/lib/hooks/useAgents.ts                # add useAgentConfigFiles / useAgentConfigFile / useAgentMcpInstall
frontend/src/i18n/locales/{en,zh}.json             # agents.config.* / agents.mcp.* strings

The agent detail page (/agents/:name) is a simple Overview + Config files detail page: an Overview tab summarising the agent's registered config and a Config files tab rendering its known config files in a read-only viewer with a format label and open-in-external-editor / reveal-in-file-manager affordances for each file and its containing folder.

Phasing

Phase 0 — Research (closed in conversation)

  • Alternative: separate agents table outside the Resource framework → rejected (loses audit/CRUD/UI uniformity; no future-proofing for agent-as-peer).
  • Alternative: bundle agent into 005 spec → rejected after re-evaluation (split for spec-size clarity; one PR delivers both).
  • Discovery heuristic: presence of a known marker directory (the type's default_config_dir) surfaces that type as a candidate. Future spec may add command-on-PATH detection.

The base registry (types/config/service/discovery, REST/CLI/desktop CRUD) already shipped on this branch. The phases below cover the v2 increment: narrow to two types, config-file view + edit, and one-click Coffer-MCP install.

Phase 1 — Type narrowing + contracts

  • Remove claude_desktop and cursor from AgentType (enum, _DISPLAY, _default_config_dir); rename codex_clicodex (display "OpenAI Codex"). Update OpenAPI enum, data-model, quickstart, frontend type dropdown, and all tests referencing the dropped/renamed types.
  • Add tomlkit to backend runtime deps.

Phase 2 — Config-file domain + backend (TDD)

  1. Domain: agent/config_files.pyConfigFileFormat, ConfigFileSpec, config_files_for, spec_for, validate_content; the allowlist resolves against the agent's config_dir. New errors ConfigFileNotAllowed, ConfigFileFormatInvalid. New audit event agent_config_file_written. Unit tests first.
  2. Infrastructure: config_file_store.py — read, stat; atomic write + .bak for config-file saves and the Coffer-MCP install. Integration tests with a tmp dir.
  3. Application: AgentConfigFileService (list/read) over ConfigFileStorePort. Integration tests: list/read/missing/unknown-key.
  4. Surfaces: agent_config_routes.py (HTTP GET), coffer agent config ls|cat (CLI), composition wiring. Contract + CLI tests.

Phase 3 — Coffer-MCP install (TDD)

  1. Domain: agent/mcp_install.pyapply_install/apply_uninstall/is_installed for json (~/.claude.json) and toml (config.toml via tomlkit). Pure-text unit tests for both formats incl. idempotency.
  2. Application: AgentMcpService (status/install/uninstall) + shim-path resolver (COFFER_MCP_SHIM_PATHshutil.which → interpreter scripts dir → bundled fallback → ShimNotFound). New audit events agent_mcp_installed/agent_mcp_uninstalled. Integration tests incl. install-twice, uninstall-absent, shim-not-found.
  3. Surfaces: HTTP GET/POST/DELETE …/mcp-install, coffer agent mcp status|install|uninstall. Contract + CLI tests.

Phase 4 — Frontend

  • AgentConfigPanel — list config files and open one in a read-only content view (with a format label) plus open-in-external-editor / reveal-in-file-manager for the file and its containing folder. AgentMcpInstall — status badge + install/uninstall toggle.
  • The agent detail page is a simple Overview + Config files detail page.
  • FolderPicker — pick a custom config_dir without typing a path: the OS-native directory dialog in the packaged desktop app, the daemon-backed GET /fs/browse folder browser on the web. The add/edit forms make the agent name optional (server derives the per-type default when omitted).
  • Hooks via TanStack Query + openapi-fetch; i18n strings in English + Simplified Chinese (agents.config.*, agents.mcp.*).
  • e2e (e2e/web/specs/shell_agents.spec.ts): view a config file read-only (and its open/reveal affordances); install Coffer MCP and observe the status flip.

Phase 5 — Acceptance + verify

  • Every acceptance scenario in spec.md has at least one test with @pytest.mark.acceptance(spec="004-agent-registry", scenario="…").
  • make verify (and make verify-all for e2e) green on macOS + Linux.

Risks / unknowns

  • GUI / venv PATH — a desktop- or venv-launched daemon does not inherit the shell PATH (and its sys.executable may be a symlink to the base interpreter), so a bare coffer-mcp-shim command may not resolve. Mitigation: resolve to an absolute path at install time (shutil.which → the interpreter's sysconfig scripts dir → bundled dist/ fallback), fail loudly if none exist.
  • ~/.claude.json reserialization — installing the MCP entry reserializes the whole JSON file (stdlib json, indent=2), producing a large diff. Acceptable and recoverable via .bak; documented.
  • TOML formatting — Codex config.toml edits use tomlkit to preserve the user's comments/layout rather than reserializing.

Workspace amendment (delivered on feature/agent-workspace)

The spec.md workspace amendment (FR-025..FR-037) turned the agent detail page into a full workspace. New modules per layer:

  • Domain: agent/mcp_entries.py (parse/remove/toggle MCP entries + secret-key detection + adopt transport mapping), agent/plugin_state.py (Codex/Claude plugin + marketplace parsing, documented-surface-only writes), agent/scan.py (per-type skill scan locations for spec 005's unmanaged scan), and config_files.py v2 (ConfigFileKind directory entries, instructions rename, subagents/hooks entries, validate_child_relpath).
  • Application: agent/mcp_entry_service.py (list/toggle/remove/adopt with keychain-routed secrets and registration-first rollback), agent/plugin_service.py (list/toggle/Codex-uninstall + cache handling), config_file_service.py v2 (directory children read/write/delete, content fingerprints with ConfigFileStale → 409, memory-block notice).
  • Surfaces: http/agent_workspace_routes.py (/agents/{name}/mcp-entries*, /agents/{name}/plugins*), agent_config_routes.py v2 (/config-files/{key}/files/{relpath} GET/PUT/DELETE + fingerprint fields), agent_routes.py (AgentPatch/AgentOut follow-policy fields); CLI cli/agent_workspace_cmd.py attached onto agent_cmd.py's typers (coffer agent mcp entries|remove-entry|toggle-entry|adopt, coffer agent plugin list|enable|disable|uninstall, coffer agent config files|write|rm, coffer agent follow).
  • Frontend: agent detail tabs AgentMcpServersTab (gateway + direct entries, adopt dialog), AgentPluginsTab, and AgentConfigFilesEditor (read-only viewer over single files and directory children — content rendered read-only with open-in-external-editor / reveal for the file and its folder, plus the memory-block notice; programmatic write/create/delete stays on REST/CLI).

New audit events: agent_config_file_deleted, agent_mcp_entry_removed, agent_mcp_entry_adopted, agent_plugin_toggled, agent_plugin_uninstalled. No storage changes — every workspace facet is derived from the agent's own files at read time.

Open items deferred to future specs

  • Agent type extension beyond the two supported (Claude Desktop chat app, Gemini CLI, GitHub Copilot) — each adds an enum value, scanner, and config-file allowlist.
  • Agent health check (is the install still present at the registered path) — separate spec.
  • Agent as MCP peer (expose another agent as a callable tool through Coffer's MCP gateway) — exploratory.