Skip to content

Cross-CLI agent-context sync (bernstein agents-md)

Teams running more than one CLI coding agent (Cursor + Claude Code + Codex is the common 2026 stack) end up maintaining three to five near-identical context files. They drift over weeks: the test command diverges, conventions disagree, the architectural overview goes stale in one but not the others, and an agent picks the wrong file.

bernstein agents-md is one source of truth (AGENTS.md) and N agent-specific consumers that stay in sync via one command.

What it generates

The canonical IR is the AGENTS.md document - AAIF-aligned, plain markdown, no frontmatter - derived from the repository:

Source Drives
pyproject.toml/package.json Project name, scripts, language stack
README.md first paragraph Overview
Module docstrings under src/ Module map
Git history + default branch Git workflow
templates/roles/* Agent roles
.editorconfig, ruff/eslint config Code conventions
.sdd/agents-md/*.md Curated overlay sections

From the same canonical IR the bridge (agents_md_bridge.py) translates to four downstream formats every supported CLI actually reads on disk in 2026:

Target On-disk path(s)
Cursor .cursor/rules/<key>.mdc (per-section, YAML frontmatter)
Claude Code CLAUDE.md at repo root
Aider CONVENTIONS.md + .aider.conf.yml (read: entry)
Goose .goosehints at repo root

The legacy .cursorrules is intentionally not emitted - Cursor's docs no longer document it; .cursor/rules/*.mdc is the supported surface. Devin's .devin.yaml is deferred until the schema stabilises.

CLI verbs

bernstein agents-md generate            # print canonical AGENTS.md
bernstein agents-md write --target T    # write one target's files
bernstein agents-md sync                # write all targets in one go
bernstein agents-md verify              # CI gate: exit 1 on any drift
bernstein agents-md diff                # human-readable unified diff

--target T accepts canonical | cursor | claude | aider | goose (plus all for verify/diff). All commands take --workdir PATH to operate on another repo.

Drift-and-reconcile demo

The killer feature is closing the loop: notice when an agent file disagrees with AGENTS.md, then reconcile in one shot.

1. A repo with two stale files

$ cat AGENTS.md | grep -A1 'Build & test'
## Build & test

| Task | Command |
|------|---------|
| Tests | `uv run pytest` |

$ cat CLAUDE.md | grep -A1 'Tests'
| Tests | `python -m pytest` |   # ← stale, predates uv migration

2. verify flags the drift in CI

$ bernstein agents-md verify
DRIFT    CLAUDE.md  (target=claude)
         first diff at offset 312: actual='python -m pytest' expected='uv run pytest'
DRIFT    CONVENTIONS.md  (target=aider)
         first diff at offset 312: actual='python -m pytest' expected='uv run pytest'

2 file(s) drift. Run `bernstein agents-md sync` to fix.
$ echo $?
1

verify exits non-zero so it can sit in .github/workflows/ci.yml and gate merges. Bernstein's own CI uses it:

- name: AGENTS.md cross-CLI sync drift check
  run: uv run bernstein agents-md verify --workdir .

3. diff prints a reviewable hunk

$ bernstein agents-md diff --target claude

# CLAUDE.md  (target=claude)

--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -312,7 +312,7 @@
 | Task | Command |
 |------|---------|
-| Tests | `python -m pytest` |
+| Tests | `uv run pytest` |

diff exits 0 either way - it is informational.

4. sync rewrites every target from the canonical source

$ bernstein agents-md sync
  · AGENTS.md  (canonical)
  · .cursor/rules/overview.mdc  (cursor)
  · .cursor/rules/module-map.mdc  (cursor)
  · ...
  · CLAUDE.md  (claude)
  · CONVENTIONS.md  (aider)
  · .aider.conf.yml  (aider)
  · .goosehints  (goose)
Synced 12 file(s) across 5 target(s) under .

$ bernstein agents-md verify
OK       all 12 file(s) in sync

After sync, verify is green. The five surfaces all carry the same test command, the same module map, the same role definitions. The loop is one command.

Authoring overlays

Auto-derivation handles ~80% of any real repo. The remaining 20% - team-specific gotchas, project conventions that aren't pulled from pyproject.toml - lives under .sdd/agents-md/:

.sdd/agents-md/
├── conventions.md      # appended to the conventions section
└── custom-policies.md  # rendered as a "custom" section verbatim

Both files are plain markdown. The generator picks them up automatically; no further config.

CI integration

Add this step to keep AGENTS.md and the four downstream files locked together over time:

# .github/workflows/ci.yml
- name: AGENTS.md cross-CLI sync drift check
  run: uv run bernstein agents-md verify --workdir .

If a contributor edits a module docstring, role template, or pyproject.toml script that flows into the canonical IR, this step fails until they run bernstein agents-md sync locally and commit the regenerated outputs. That is the dogfood-the-killer-feature loop.

Implementation notes

The canonical IR (list[AgentsMdSection]) is the only source of truth. Each target's renderer is a pure function from sections to a {relative_path: file_content_str} map. No target invents new content; if a target's format requires a field the IR doesn't carry (e.g. Cursor's description frontmatter), it is derived from the section's existing fields, never invented.

Modules:

  • src/bernstein/core/knowledge/agents_md_generator.py - section builders + canonical render.
  • src/bernstein/core/knowledge/agents_md_bridge.py - per-target translators.
  • src/bernstein/cli/commands/agents_md_cmd.py - Click group with generate, write, sync, verify, diff.

References: