SC-1 Agent-config schema, migrations, builtin sync #623

Closed
opened 2026-05-01 10:32:49 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As a platform engineer, I want the per-kind tables, the shared revision/secret tables, the builtin-sync boot pass, and the resolver landed and unwired from any caller, so that every artifact is queryable from DB but production paths still consume the existing in-process config and we can layer the migration story-by-story behind a feature flag.

Acceptance criteria

Migration

  • New migration apps/server/src/infrastructure/database/migrations/NNN-agent-config.ts creates skill, system_prompt, plugin_binding, plugin_marketplace, mcp_server, agent_type_config, config_revision, secret, secret_access_log tables per the spec's schema block.
  • Indexes: idx_config_revision_lookup on (kind, artifact_id, created_at DESC), idx_secret_access_log_lookup on (secret_id, accessed_at DESC), plus per-table UNIQUE(name, scope, agent_type, instance_id) covering uniqueness for skills / plugins / MCP / marketplaces / system prompts.
  • Foreign keys to agents(id) ON DELETE CASCADE for every instance_id column so dropping an instance reaps its overrides.

Builtin sync

  • New apps/server/src/domain/agent-config/builtin-sync.ts exporting syncBuiltinsFromRepo(). Reads /skills/*.md, config/agents.json, config/mcp-builtin.json, config/marketplaces-builtin.json. Upserts scope = 'builtin' rows; drift detection via sha256 stored as builtin_hash.
  • config/mcp-builtin.json and config/marketplaces-builtin.json are seeded from the current ~/.config/claude-hooks/agent-env/*/.claude.json and settings.json. Secret values replaced by ${SECRET:<NAME>} placeholders. Document the placeholder convention in the file headers.
  • main.ts calls syncBuiltinsFromRepo() once during startup, after the migration runner.

Resolver

  • New apps/server/src/domain/agent-config/resolver.ts exports resolveSkill, resolveSystemPrompt, resolvePlugins, resolveMarketplaces, resolveMcpServers.
  • Shared pickFromLadder helper: returns the most-specific row across instance → agent_type → global → builtin. First-match-wins for string-shaped artifacts.
  • Per-kind merge for collection-shaped artifacts (plugin_binding, mcp_server, plugin_marketplace): each layer adds rows; an instance-scope row with enabled = false for a plugin name shadows a higher layer's enabled row for the same name.

Tests

  • Unit: pickFromLadder returns the most-specific row across all four scope shapes, including null/skip cases.
  • Unit: builtin-sync upserts unchanged rows as no-ops, updates body when the file's sha256 differs.
  • Unit: resolver layers (global row shadows builtin, agent_type shadows global, instance shadows agent_type).
  • Unit: collection merge — instance-scope enabled = false shadows agent-type enabled = true for the same plugin name.

Out of scope

  • Render-to-disk wiring (SC-2).
  • HTTP routes (SC-7).
  • Replacing existing loadSkill / system_prompt_template consumers (SC-3, SC-4).

References

  • specs/agent-config-customization.md §Architecture and §Story SC-1
  • apps/server/src/infrastructure/database/ — existing migrations + DAOs
  • apps/server/src/domain/analytics/skill-loader.tsloadSkill filesystem reader (consumed by builtin-sync)
  • apps/server/src/shared/config/agents-config-schema.tsagents.json schema (read by builtin-sync)
## User story As a platform engineer, I want the per-kind tables, the shared revision/secret tables, the builtin-sync boot pass, and the resolver landed and unwired from any caller, so that every artifact is queryable from DB but production paths still consume the existing in-process config and we can layer the migration story-by-story behind a feature flag. ## Acceptance criteria ### Migration - [ ] New migration `apps/server/src/infrastructure/database/migrations/NNN-agent-config.ts` creates `skill`, `system_prompt`, `plugin_binding`, `plugin_marketplace`, `mcp_server`, `agent_type_config`, `config_revision`, `secret`, `secret_access_log` tables per the spec's schema block. - [ ] Indexes: `idx_config_revision_lookup` on `(kind, artifact_id, created_at DESC)`, `idx_secret_access_log_lookup` on `(secret_id, accessed_at DESC)`, plus per-table `UNIQUE(name, scope, agent_type, instance_id)` covering uniqueness for skills / plugins / MCP / marketplaces / system prompts. - [ ] Foreign keys to `agents(id) ON DELETE CASCADE` for every `instance_id` column so dropping an instance reaps its overrides. ### Builtin sync - [ ] New `apps/server/src/domain/agent-config/builtin-sync.ts` exporting `syncBuiltinsFromRepo()`. Reads `/skills/*.md`, `config/agents.json`, `config/mcp-builtin.json`, `config/marketplaces-builtin.json`. Upserts `scope = 'builtin'` rows; drift detection via sha256 stored as `builtin_hash`. - [ ] `config/mcp-builtin.json` and `config/marketplaces-builtin.json` are seeded from the current `~/.config/claude-hooks/agent-env/*/.claude.json` and `settings.json`. Secret values replaced by `${SECRET:<NAME>}` placeholders. Document the placeholder convention in the file headers. - [ ] `main.ts` calls `syncBuiltinsFromRepo()` once during startup, after the migration runner. ### Resolver - [ ] New `apps/server/src/domain/agent-config/resolver.ts` exports `resolveSkill`, `resolveSystemPrompt`, `resolvePlugins`, `resolveMarketplaces`, `resolveMcpServers`. - [ ] Shared `pickFromLadder` helper: returns the most-specific row across `instance → agent_type → global → builtin`. First-match-wins for string-shaped artifacts. - [ ] Per-kind merge for collection-shaped artifacts (`plugin_binding`, `mcp_server`, `plugin_marketplace`): each layer adds rows; an instance-scope row with `enabled = false` for a plugin name shadows a higher layer's enabled row for the same name. ### Tests - [ ] Unit: `pickFromLadder` returns the most-specific row across all four scope shapes, including null/skip cases. - [ ] Unit: builtin-sync upserts unchanged rows as no-ops, updates body when the file's sha256 differs. - [ ] Unit: resolver layers (`global` row shadows `builtin`, `agent_type` shadows `global`, `instance` shadows `agent_type`). - [ ] Unit: collection merge — instance-scope `enabled = false` shadows agent-type `enabled = true` for the same plugin name. ## Out of scope - Render-to-disk wiring (SC-2). - HTTP routes (SC-7). - Replacing existing `loadSkill` / `system_prompt_template` consumers (SC-3, SC-4). ## References - `specs/agent-config-customization.md` §Architecture and §Story SC-1 - `apps/server/src/infrastructure/database/` — existing migrations + DAOs - `apps/server/src/domain/analytics/skill-loader.ts` — `loadSkill` filesystem reader (consumed by builtin-sync) - `apps/server/src/shared/config/agents-config-schema.ts` — `agents.json` schema (read by builtin-sync)
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#623
No description provided.