Skip to content

Support repo-local Kanon overlays for project-owned agent context #33

Description

@kelos-bot

🤖 Kelos Strategist Agent @gjkim42

Area: New Use Cases

Summary

The highest-value new use case I found is multi-repo teams that need a central agent baseline plus repository-owned context.

Kanon already has the right destination shape: --project can render project-scoped AGENTS.md, CLAUDE.md, .mcp.json, .codex/, .claude/, and skill files. But the source shape is still a single Kanon home. There is no way for an org/platform team to own the global baseline while each repository owns its local instructions, MCP servers, hooks, and skills in that repository.

That leaves teams with bad choices:

  • Put every repo’s instructions/MCP/hooks into one central ~/.config/kanon repo, which does not scale across dozens of services and makes repo owners dependent on a central config maintainer.
  • Keep per-repo AGENTS.md, CLAUDE.md, .mcp.json, etc. by hand, which loses Kanon’s cross-agent compilation and import/apply lifecycle.
  • Copy a baseline into every repo, which drifts quickly.

Proposal: add explicit project overlay support so kanon apply --project <repo> can compose the central Kanon config with a repo-local partial config, then render the merged result into that project only.

Evidence from the current codebase

  • The README defines source state as one kanon.yaml plus instructions/, skills/, and hooks/ in the Kanon home (README.md:14-15). There is no repo-local source layer.
  • LoadConfig reads exactly one config path, defaulting to <home>/kanon.yaml (internal/core/config.go:27-45).
  • TargetOptions has KanonHome, UserHome, Project, and Agent only (internal/core/types.go:73-78). Project controls destination paths, not source composition.
  • The Codex adapter switches destinations when opts.Project != "": project AGENTS.md, project .codex, and project .agents/skills (internal/core/codex.go:17-24).
  • The Claude adapter similarly switches destinations to project CLAUDE.md, project .claude, and project .mcp.json (internal/core/claude.go:17-25).
  • Source assets are still resolved from opts.KanonHome: readInstruction(opts.KanonHome, ...) and renderSkills use ResolvePath(opts.KanonHome, ...) (internal/core/render.go:55-117).
  • import --project can read project-scoped destination files, but WriteImport always writes the resulting source to <home>/kanon.yaml and assets under the Kanon home (internal/core/import.go:62-75). There is no way to write an imported project config back into the repository that owns it.

Net: Kanon can render project-scoped files, but it cannot model project-scoped source ownership.

Existing issue overlap check

This is intentionally distinct from the open generated issues I reviewed:

External signal

Current agent ecosystems already expect global + repository/project layers:

The pattern is clear: reusable defaults live above the repo, while repo-specific context lives with the repo. Kanon should compile both instead of forcing users to choose one owner.

Proposed design

1. Add an explicit project overlay flag

Start explicit for safety; do not auto-load arbitrary repo config by default.

kanon render --project . --overlay .kanon/project.yaml
kanon diff   --project . --overlay .kanon/project.yaml
kanon apply  --project . --overlay .kanon/project.yaml

--overlay loads a partial Kanon config after the base <home>/kanon.yaml. Overlay asset paths resolve relative to the overlay file directory, not the central Kanon home.

Suggested repo layout:

repo/
  .kanon/
    project.yaml
    instructions/repository.md
    skills/release-notes/SKILL.md
  AGENTS.md          # rendered
  CLAUDE.md          # rendered
  .mcp.json          # rendered for Claude project scope
  .codex/hooks.json  # rendered for Codex project scope

Central baseline:

# ~/.config/kanon/kanon.yaml
version: 1
instructions:
  files:
    - instructions/org-baseline.md
skills:
  - name: secure-review
mcp:
  servers:
    github:
      type: http
      url: https://mcp.githubcopilot.com/mcp/
      targets: [claude, codex]
hooks: []

Repository overlay:

# repo/.kanon/project.yaml
version: 1
instructions:
  files:
    - instructions/repository.md
skills:
  - name: release-notes
    path: skills/release-notes
mcp:
  servers:
    local-api:
      command: ./tools/mcp-local-api
      args: [--root, "${CLAUDE_PROJECT_DIR:-.}"]
      targets: [claude]
hooks:
  - name: repo-tests
    event: PostToolUse
    matcher: Edit|Write
    command: make
    args: [test]

Rendered project instructions should be ordered base then overlay, matching Codex’s global-then-project layering model.

2. Keep merge rules tiny and deterministic

Recommended MVP merge semantics:

  • version must match the base schema version.
  • instructions.files: append base files, then overlay files.
  • skills: append by name; duplicate names across layers are an error unless a later replace: true field is added.
  • mcp.servers: merge by server name; duplicate names are an error unless explicitly replaced.
  • hooks: append; duplicate hook names are an error unless explicitly replaced.
  • metadata: shallow merge, overlay wins.

The important part is to avoid a broad “deep merge everything” system in v1. Conflicts should be visible rather than surprising.

3. Constrain overlays to project scope

Because overlay configs can define MCP servers and hooks that run commands, they should be treated as repo-trusted input:

This lets a developer consciously choose to apply a repository’s agent context without letting a random checkout mutate their global agent setup.

4. Add import support for repo-owned overlays

Add a project import path that writes the discovered project context back to the repository overlay instead of centralizing it:

kanon import --project . --write-overlay .kanon/project.yaml

Behavior:

  • Read existing project AGENTS.md, CLAUDE.md, .mcp.json, .codex/hooks.json, .claude/settings.json, and project skill dirs using the current project import logic.
  • Write the neutralized result into repo/.kanon/project.yaml plus repo/.kanon/instructions/ and repo/.kanon/skills/.
  • Leave the central ~/.config/kanon/kanon.yaml alone.

This is the adoption path for repositories that already have agent files checked in.

Use cases unlocked

  1. Multi-repo product org: platform owns the org baseline; service teams own .kanon/project.yaml in each repo for service-specific runbooks, test commands, and local MCP servers.
  2. Open-source maintainers: a repo can publish one neutral Kanon overlay and let contributors render Codex + Claude config consistently instead of hand-maintaining multiple agent-native files.
  3. Consultancies / agencies: engineers keep their personal baseline while client repos carry project-specific constraints and tools without polluting the engineer’s global config.
  4. Monorepos: start with one repo overlay, then later combine with Make Kanon fleet-aware: per-machine / per-role / per-OS variation via profiles + 'when:' selectors #20 when: selectors or a future nested overlay if subteams need narrower rules.

Suggested MVP scope

  1. Add --overlay <path> to render, diff, and apply; require --project with it.
  2. Add a LoadConfigLayers(basePath, overlays...) helper that tracks each layer’s root directory for path resolution.
  3. Extend instruction/skill source resolution so each item resolves against its owning layer root.
  4. Implement the conservative merge rules above with clear duplicate-name errors.
  5. Add tests proving base+overlay instructions append in order, overlay assets resolve from the repo, duplicate MCP/server names fail, and overlay use cannot write user-global files.
  6. Add import --project --write-overlay <path> as the second PR if the render/apply layering lands cleanly.

Backward compatibility

  • Existing kanon.yaml files and commands are unchanged.
  • No overlay is loaded unless the user passes the new flag.
  • Existing --project behavior remains the same when no overlay is specified.
  • The overlay format reuses the existing schema, so adoption is incremental.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions