Skip to content

ADR-0005 — Sync Agent Skills at Session Start via UserPromptSubmit Hook

Status: accepted Deciders: DeAcero AI Agent Guild Date: 2026-03-18 Commits: 5f4d78a (feat(sync): add cornerstone sync hook)


Context and Problem Statement

Each project generated from the Cornerstone template receives a static copy of .agents/skills/ at generation time. As the template evolves — new skills added, existing skills improved, protocols updated — generated projects diverge silently. There is no mechanism to notify teams of updates, and requiring a manual pull from the template is friction that teams will ignore.

The problem is guaranteed skill drift: a project generated six months ago may operate with significantly outdated or incorrect agent skills while its team is unaware.


Decision Drivers

  • Skills must stay current without requiring manual team intervention
  • Sync must not block the developer workflow (must be transparent and fast-path-exit when up to date)
  • Sync must fail silently — a missing gh auth or network issue must never crash a session
  • The solution must work within Claude Code's existing extension points
  • Teams must be able to force a sync when they need the latest skills immediately

Considered Options

  1. UserPromptSubmit hook — run sync_agents.sh at every session start via Claude Code hooks; throttle to once per 24h
  2. Manual script — document bash tools/sync_agents.sh as a step teams must run periodically
  3. CI job — GitHub Actions workflow on a schedule that PRs skill updates into each generated repo
  4. Submodule.agents/skills/ as a git submodule pointing to deagentic/cornerstone
  5. KDB MCP — future: query a central Knowledge Database via MCP at session start

Decision Outcome

Chosen option: Option 1UserPromptSubmit hook with 24h throttle.

Rationale:

  • Option 2 (manual) will be skipped in practice. Human nature guarantees drift.
  • Option 3 (CI job) requires each generated project to be registered centrally, creates noisy PRs, and does not cover local developer sessions.
  • Option 4 (submodule) is fragile: submodule state diverges easily, git pull --recurse-submodules is non-obvious, and detached HEAD states confuse non-git-expert teams.
  • Option 5 (KDB MCP) is the correct long-term solution but does not exist yet. This decision explicitly plans for it: sync_agents.sh is designed so that its internal implementation can be replaced with a KDB query without changing its CLI contract.
  • Option 1 is transparent, automatic, and fails safely. Claude Code's UserPromptSubmit hook fires before the first prompt is processed, making it the ideal slot for session initialization work.

Positive Consequences

  • Skills stay current without any team action beyond the initial gh auth login
  • Teams that open a project after a long gap automatically get the latest skills
  • The 24h throttle means no perceptible delay in normal daily use
  • --force flag allows immediate sync when needed

Negative Consequences

  • Requires gh auth login as a hard prerequisite — teams without access to the private deagentic/cornerstone repo cannot sync
  • 24h staleness window means a skill fix may not reach a team until the next day
  • Sync silently does nothing if gh is not installed, which may confuse debugging

Implementation Details

tools/sync_agents.sh

Core logic:

# 1. Read .agents/.last_sync timestamp
# 2. If < 24h since last sync and not --force → exit 0 (silent)
# 3. Verify gh auth (gh auth status) → exit 0 silently if not authenticated
# 4. gh repo clone deagentic/cornerstone /tmp/cornerstone-sync --depth=1
# 5. rsync -a --delete /tmp/cornerstone-sync/.agents/skills/ .agents/skills/
# 6. Write current timestamp to .agents/.last_sync

The script stores PROJECT_SLUG="{{cookiecutter.project_slug}}" as a runtime shell variable. This is intentional: at generation time, cookiecutter replaces the Jinja2 expression. At runtime (every sync), the already-rendered value is used by the script.

_copy_without_render requirement

tools/sync_agents.sh must be listed in _copy_without_render in cookiecutter.json. Without this, {{cookiecutter.project_slug}} inside the script would be re-rendered by cookiecutter at generation time and could conflict with other template variables if the script itself were treated as a Jinja2 template. The _copy_without_render directive copies the file as-is, so the already-rendered literal value is preserved.

Hook wiring in .claude/settings.json

{
  "hooks": {
    "UserPromptSubmit": [{
      "matcher": "",
      "hooks": [{"type": "command", "command": "bash tools/sync_agents.sh 2>/dev/null || true"}]
    }]
  }
}

The 2>/dev/null || true wrapper ensures that any error in the sync script — network failure, missing gh, permission denied — is completely swallowed and never surfaces to the developer.

Future evolution path

The sync_agents.sh contract is stable. When the KDB becomes available, only the internal implementation changes:

TODAY                                  FUTURE
─────────────────────────────          ─────────────────────────────
sync_agents.sh                         sync_agents.sh
  gh repo clone cornerstone    →         MCP query → KDB
  rsync .agents/skills/                  skills + business rules
                                         + accumulated learnings

The bash tools/sync_agents.sh CLI contract does not change. Projects do not need to be updated.


Pros and Cons of Options

Option 1: UserPromptSubmit hook (chosen)

  • Good, because fully automatic — zero team action after initial setup
  • Good, because fails silently — never blocks a session
  • Good, because designed for future KDB migration (same contract)
  • Bad, because requires gh auth login prerequisite

Option 2: Manual script

  • Good, because no dependencies beyond the script file itself
  • Bad, because teams will not run it — drift is guaranteed

Option 3: CI job

  • Good, because automatic and does not depend on local gh auth
  • Bad, because noisy (PRs per project per sync), requires central project registry

Option 4: Git submodule

  • Good, because native git — no extra tooling
  • Bad, because submodule drift is the exact same problem in a different form; teams routinely forget --recurse-submodules

Option 5: KDB MCP (future)

  • Good, because skills + business rules + learnings in one query
  • Bad, because KDB does not exist yet; blocks on significant infrastructure work

  • Implemented in: {{cookiecutter.project_slug}}/tools/sync_agents.sh
  • Hook configured in: {{cookiecutter.project_slug}}/.claude/settings.json
  • Parent ADR: ADR-0004 (project-level settings)
  • Future evolution: docs/future.md — KDB section