Skip to content

RFD 0002: AGENTS.md primary, tool-specific files via translator plugins

Summary

The v2 framework writes AGENTS.md as the canonical project / per-folder agent-instruction file. Tool-specific variants (CLAUDE.md for Claude Code, .cursor/rules/*.md for Cursor, .codex/ for Codex, etc.) are produced by translator plugins the user installs on demand. The framework binary has zero references to CLAUDE.md or .claude/ — enforced by CI.

Translator plugins are symmetric: claude-translator, codex-translator, cursor-translator, continue-translator. None is privileged. v2.0 ships claude-translator and codex-translator ([[0007|RFD 0007]]); other tools' translators are community plugins.

The default agent = "claude" in samuel.toml is a default, not lock-in — users change it without reinstalling anything, and the framework treats Claude no differently than any other agent.

Problem statement

v1 writes both CLAUDE.md and AGENTS.md with identical content as a mirror. The duplication is a defensive cross-tool gesture from a time when Claude Code was the assumed-primary tool and AGENTS.md was the just-added cross-tool format ([[../../wiki/entities/sync-claude-md]]).

In 2026, the agents.md standard has matured. Cursor, Codex, Claude Code, GitHub Copilot, Continue, and Aider all read AGENTS.md (or are converging on it). Writing two files is no longer defensive — it's redundant for users of the standard, and presumptuous for users whose tool doesn't read CLAUDE.md.

More importantly: v2 positions as "Rails for coding assistants" plural ([[../../wiki/synthesis/positioning-rails-for-coding-assistants]]). Hardcoding Claude Code's filename as primary undermines the positioning. A user who installs Samuel because it's tool-agnostic should not get a CLAUDE.md by default.

Requirements

  • The framework writes one canonical agent-instruction file: AGENTS.md (root + per-folder).
  • Tool-specific files (CLAUDE.md, Cursor rules, Codex configs) are written by translator plugins that the user installs.
  • No translator plugin is privileged in the framework — all are equal-status plugins resolved through the standard plugin loader ([[0001|RFD 0001]]).
  • A user who installs no translator plugin gets a fully functional, tool-agnostic project layout.
  • A user with multiple tools (Claude + Codex) installs both translators; each emits its tool's files.
  • The agnostic invariant is enforceable in CI — grep for "CLAUDE.md" literals or ".claude/" paths in internal/ fails the build.

Constraints

  • agents.md is a young standard. v2 follows it as-is at v2.0 launch but accepts the spec may evolve.
  • v1 users have existing CLAUDE.md files. v2 must coexist gracefully (detect, warn, don't clobber).
  • Translator plugins are WASM-tier ([[0001|RFD 0001]]) — small, sandboxed, no host runtime needed.

Background

Where v1 sits

v1's sync.go ([[../../wiki/entities/sync-claude-md]]) emits both CLAUDE.md and AGENTS.md at every per-folder write. The bytes are identical. The autogen marker (<!-- Auto-generated by Samuel) is in both. User-customization preservation works against both.

v1's template template/CLAUDE.md has one line distinguishing it from template/AGENTS.md (different "this is the primary" callout); the rest is byte-identical.

Effectively: v1 already supports AGENTS.md. It just doesn't commit to it as primary.

The agents.md standard ecosystem

By mid-2026, AGENTS.md is the de facto cross-tool standard:

  • Claude Code: reads AGENTS.md and CLAUDE.md (the latter as backward compat).
  • Cursor: reads AGENTS.md plus .cursor/rules/*.md for finer-grained scoping.
  • Codex: reads AGENTS.md; some Codex setups also use .codex/config.toml.
  • GitHub Copilot: reads AGENTS.md (recent addition; was .github/copilot-instructions.md originally).
  • Continue: reads AGENTS.md plus .continue/ config.
  • Aider: reads AGENTS.md plus .aider.conf.yml.

Writing AGENTS.md is the highest-coverage single file. Tool-specific files are additive value when the user has that specific tool.

What translator plugins do

A translator plugin's job: read the AGENTS.md the framework wrote, emit the tool-specific equivalent. Same content, tool-specific format and location.

claude-translator is mostly trivial — Claude Code reads AGENTS.md natively in 2026. The translator's value is:

  • Emit CLAUDE.md as a verbatim copy for users on older Claude Code versions.
  • Install .claude/settings.json with PreToolUse hook scaffolding ([[../../wiki/concepts/claude-code-hooks]]).
  • Emit per-folder CLAUDE.md mirrors when AGENTS.md is per-folder.

codex-translator is similar — Codex aligns with AGENTS.md too. The plugin emits .codex/config.toml if the user's Codex setup uses one. Minimal work; the value is in the symmetry with claude-translator (proving the agnostic story).

Translator plugins for tools that don't read AGENTS.md (a hypothetical 2026 holdout) do more work: parse AGENTS.md, emit the tool-specific format under the tool's filesystem layout.

Options considered

Option A: AGENTS.md primary + translator plugins (chosen)

Framework writes AGENTS.md only. Tool-specific files come from translator plugins.

Pros: - Single source of truth — content lives in AGENTS.md, translators are projections. - Agnostic by construction (CI-enforced via [[../../wiki/concepts/agnostic-by-design]]). - Symmetric across tools — claude-translator and codex-translator are peer plugins, not "Claude has special status." - Scales to any future agent — new tools get a new translator plugin without framework changes. - Aligns with the agents.md standard's stated goal of cross-tool unification. - Users who don't install any translator still have a fully functional agent-instruction setup (every modern coding assistant reads AGENTS.md).

Cons: - v1 users who depended on CLAUDE.md being there by default lose that. Mitigated by claude-translator being trivially installable. - Two files (AGENTS.md + CLAUDE.md) for Claude users with translator installed — temporary redundancy until Claude Code drops CLAUDE.md. - Translator plugin authors carry the per-tool format knowledge — Cursor's rules dir, Codex's config, etc.

Effort: Low for the framework (drop one file write from sync.go). Medium for the two translator plugins ([[0007|RFD 0007]]).

Option B: CLAUDE.md primary (v1 status quo)

Framework writes CLAUDE.md as primary, AGENTS.md as compat mirror. Same as v1.

Pros: - Zero migration for v1 users. - "It works with Claude Code" out of the box.

Cons: - Reverses the agnostic positioning. v2 ships claiming "Rails for coding assistants" plural while privileging one in the default output. - Codex users get a CLAUDE.md they don't read, must rename or symlink manually. - Cursor users get a CLAUDE.md they don't read. - Doesn't scale — adding "and write Cursor rules too" requires framework changes.

Effort: Zero. Lowest in code but highest in narrative inconsistency.

Option C: Both written by the framework (v1's actual behavior — duplicate)

Framework writes AGENTS.md and CLAUDE.md identically every sync, regardless of tool.

Pros: - Most users covered by default. - v1 users' muscle memory works.

Cons: - Same duplication v1 has. Two identical files on disk per folder. Twice the autogen-marker management. Twice the file-watcher noise. - Still doesn't address Cursor, Codex's .codex/, etc. - Doesn't make the framework agnostic — it embeds the assumption that "Claude Code is one of the two tools to support by default."

Effort: Zero (it's v1's behavior). But the agnostic invariant is broken.

Option D: Tool-detected — framework auto-picks based on installed tools

samuel init runs which claude / which cursor / which codex. If found, writes the matching file format(s).

Pros: - Zero user intervention. The right files for the user's actual tooling appear.

Cons: - Detection is fragile — users uninstall tools, swap between machines, run in CI containers without their tools installed. - Framework code now knows about every supported tool's CLI name. Bloats with each new tool. - Re-introduces the per-tool branching that translator plugins are designed to remove. - Detection-time logic differs from sync-time logic (a tool installed later doesn't trigger a file emission).

Effort: Medium. Wrong design — tool detection is plugin-installation territory.

Option E: User-selected at init

samuel init --tool claude picks Claude as the format. --tool codex picks Codex. Sets a flag in samuel.toml.

Pros: - Explicit, predictable. - No detection.

Cons: - Forces the user to pick one tool. What if they use both? - Couples the framework to the list of supported tools — a flag enum like --tool {claude,cursor,codex,...} doesn't scale. - Migrating between tools requires re-init.

Effort: Medium. Worse UX than installing a translator plugin.

Decision

Adopt Option A: AGENTS.md primary, tool-specific files via translator plugins.

The decision rests on four judgments:

  1. AGENTS.md is the right canonical file in 2026. Every modern coding assistant reads it. Writing it alone covers the broadest user base. Backward-compat (older Claude Code without AGENTS.md support) is the translator plugin's job, not the framework's.

  2. Translator plugins scale; framework branching doesn't. Option D and Option E couple the framework to specific tools' filenames. Option A defers tool-specific logic to plugins; new tools get new plugins, framework unchanged.

  3. The agnostic invariant is enforceable. Option A makes "framework doesn't write CLAUDE.md" a property checkable by grep -r '"CLAUDE\.md"' internal/ in CI. Easier to enforce than nuanced "framework writes CLAUDE.md but treats it as one of two equivalent outputs" semantics from Option C.

  4. Plugin authors mirror v2's positioning. Plugin authors writing translators for other tools (Cursor, Continue, Aider) follow the same pattern: read AGENTS.md, emit tool-specific. The framework's choice imprints on every plugin author who follows.

Implementation plan

Phase 1 — framework writes AGENTS.md only (PRD 0002, week 3)

internal/sync/ produces only AGENTS.md (root + per-folder). Drop v1's CLAUDE.md emission.

// internal/sync/write.go
func WriteFolderContext(dir string, content string, opts WriteOptions) error {
    return writeWithAutogenMarker(filepath.Join(dir, "AGENTS.md"), content, opts)
    // No CLAUDE.md write. That's the claude-translator plugin's job.
}

The autogen marker convention carries forward unchanged:

<!-- Auto-generated by Samuel. Customize with folder-specific instructions. -->
<!-- AI agents load this file when working in this directory. -->

User-customized files (missing the marker) are skipped unless --force passed.

Phase 2 — sync hooks expose AGENTS.md to translator plugins (PRD 0002, week 3)

The sync system fires hooks per [[0004|RFD 0004]]:

sync.before              → plugins can pre-process (e.g., add language-detection metadata)
sync.analyze-folder      → default: heuristic scan; plugin can replace per folder
sync.write-agents-md     → default: write AGENTS.md
sync.after               → plugin slot: translators run here

Translator plugins register handlers for sync.after. The framework passes the list of AGENTS.md paths just written; the plugin emits its tool-specific files alongside.

WASM plugin API (per [[0001|RFD 0001]]):

host functions exposed to translator plugins:
  samuel.fs.read(path) → reads AGENTS.md content
  samuel.fs.write(path, bytes) → writes tool-specific file (capability-gated)
  samuel.log(level, message) → emits warnings

Phase 3 — agnostic invariant CI check (PRD 0001, week 2)

.github/workflows/agnostic-check.yml:

name: Agnostic-by-design invariant
on: [pull_request, push]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: No CLAUDE.md literal in framework code
        run: |
          if grep -rn '"CLAUDE\.md"' internal/ cmd/ template/; then
            echo "::error::Framework references CLAUDE.md by literal."
            echo "::error::Tool-specific filenames belong in translator plugins, not framework."
            echo "::error::See docs/rfd/0002.md for the agnostic-by-design invariant."
            exit 1
          fi
      - name: No .claude/ path in framework code
        run: |
          if grep -rn '"\.claude/' internal/ cmd/ template/; then
            echo "::error::Framework writes to .claude/ path."
            echo "::error::Move this to the claude-translator plugin."
            exit 1
          fi
      - run: echo "✓ Agnostic invariant holds"

Plugins are exempt — they live in their own repos, not in internal/.

Phase 4 — translator plugins (PRD 0005)

Per [[0007|RFD 0007]]:

  • samuel-claude-translator: WASM plugin. Hook handlers for sync.after. Reads each AGENTS.md the framework wrote, emits sibling CLAUDE.md. Installs .claude/settings.json with PreToolUse hook stubs at install time.
  • samuel-codex-translator: WASM plugin. Hook handlers for sync.after. Emits .codex/ files matching Codex's 2026 conventions.

Both ship at v2.0 launch. Future translators (cursor-translator, continue-translator) are community plugins.

Phase 5 — v1 coexistence handling (PRD 0002, week 5)

samuel init in a directory that already has a v1 CLAUDE.md:

  • Detect via the v1 autogen marker (<!-- Auto-generated by Samuel — same marker).
  • Warn: "v1 CLAUDE.md detected at . v2 manages AGENTS.md instead. The existing CLAUDE.md is untouched."
  • Do not delete the file.
  • If claude-translator is later installed, the plugin's init.after hook adopts the existing CLAUDE.md (replaces with translator-managed copy on next sync).

samuel doctor reports stale v1 files informational.

Acceptance criteria

  • samuel init in a clean project writes AGENTS.md only — no CLAUDE.md.
  • samuel sync regenerates AGENTS.md files; does not write CLAUDE.md.
  • grep -rn '"CLAUDE\.md"' internal/ returns no matches (CI enforced).
  • grep -rn '"\.claude/' internal/ returns no matches (CI enforced).
  • Installing claude-translator causes subsequent samuel sync runs to also emit CLAUDE.md at each AGENTS.md sibling.
  • Uninstalling claude-translator stops the CLAUDE.md emission (plugin's Uninstall reverses).
  • Installing codex-translator emits Codex-specific files; uninstall reverses.
  • Installing both translators produces both sets of files without interference.
  • Existing v1 CLAUDE.md files are detected at init, warned about, not modified by the framework.
  • samuel doctor reports unmanaged v1 CLAUDE.md files informationally.
  • AGENTS.md template at ≤150 lines passes the agents-md-check.yml CI gate.

Compatibility and migration

  • v1 users with CLAUDE.md: file stays untouched. After installing claude-translator, the next sync overwrites with translator-managed content (autogen marker preserved). Users who hand-edited their v1 CLAUDE.md without the marker have the file preserved indefinitely.
  • v1 projects with AGENTS.md: same content as CLAUDE.md in v1. v2's sync continues managing it.
  • v1 .claude/skills/ content: untouched. v2 uses .samuel/plugins/ for its own plugins. v1 skill content is left as a historical artifact until users manually clean it up.

The migration notice in docs/getting-started/migration-v1.md explains.

Risks

Risk Likelihood Mitigation
Users expect CLAUDE.md and don't see it High Migration notice covers this. claude-translator install is one command. samuel doctor could detect Claude on PATH and suggest the translator install.
Translator plugins are essential but mark as "optional" — discoverability problem High samuel doctor detects installed coding assistants and suggests matching translators. v2 docs prominently feature the translator pattern.
Translator plugin maintenance lags Claude / Codex format evolution Medium Plugin repos have their own release cadence. Bugfixes per-plugin without framework version bumps.
Two-write race condition: framework writes AGENTS.md, translator writes CLAUDE.md concurrently Low Hooks fire serially after the framework's writes complete. Sync is single-threaded per project.
WASM translator plugin can't fully emit tool-specific files (e.g., complex .cursor/rules/ structure) Medium WASM filesystem capability scoped to allow recursive writes within the target dir. If insufficient, escalate to OCI tier for that specific translator.
The agents.md standard changes mid-2026 and our v2.0 output goes stale Medium Framework writes are aligned with the standard; if the standard changes, update the AGENTS.md template (Milestone 6+ patch release). Translator plugins absorb their own tool's format changes independently.
samuel init detection of existing v1 CLAUDE.md misses edge cases Low Conservative approach: warn for any CLAUDE.md regardless of marker. Never modify without explicit confirmation.

Resolved decisions (2026-05-12)

  1. samuel doctor auto-suggests translator plugins: yes. Detects claude / cursor / codex on PATH; prints "you may want samuel install <tool>-translator." Helpful nudge without privileging any tool.

  2. Pre-existing user-managed .cursor/ directories (and equivalents): respect via autogen-marker convention. Same rule as everywhere else in the framework.

  3. Per-tool feature disparity: each translator plugin does what its target tool supports, no more. claude-translator installs .claude/settings.json hooks; codex-translator does whatever Codex's 2026 conventions are; future translators map to their own tools' surfaces. Document per-plugin.

  4. Translator plugin update flow: samuel update <plugin> runs the plugin's Install again, which re-emits tool-specific files. Already in plugin loader behavior — no special handling needed.

  5. AGENTS.md template content for v2 (final): keep these sections (rendered ≤150 lines):

  6. 4D methodology (~30 lines)
  7. Boundaries / Do Not Touch (~15 lines)
  8. Quick Reference (task classification, autonomous mode quick-ref, ~20 lines)
  9. <!-- SAMUEL_PLUGINS_START/END --> block (auto-populated from installed plugins)
  10. <!-- SAMUEL_GUARDRAILS_START/END --> block (from samuel.toml [guardrails])
  11. Project Context fillable (~10 lines)

Drop (moved to mkdocs site): full skill links table, language guide links, framework guide links, anti-patterns, when-stuck, embedded changelog.

  1. Per-folder translation: yes — translators emit per-folder equivalents wherever the framework emits per-folder AGENTS.md. Symmetric pattern.

Outcome

To be filled in post-v2.0 launch. Expected outcomes:

  • ~70% of v2 users install claude-translator (Claude Code's user base is large in 2026).
  • ~20% install codex-translator (smaller but growing).
  • ~5% of users install neither — they're on AGENTS.md-native tools (Cursor, Continue) or are tool-agnostic users.
  • No user reports "Samuel forced me to use Claude" — confirms the agnostic invariant landed.
  • Community publishes at least one third-party translator (most likely cursor-translator or aider-translator) within six months.
  • [[0001|RFD 0001]] — three plugin tiers (translator plugins use WASM tier)
  • [[0004|RFD 0004]] — methodology hooks (sync.after hook fires translators)
  • [[0007|RFD 0007]] — plugin migration (creates claude-translator + codex-translator)
  • [[../../wiki/concepts/agents-md-primary]] — wiki concept this RFD ports
  • [[../../wiki/concepts/agnostic-by-design]] — invariant this RFD upholds
  • [[../../wiki/concepts/claude-code-hooks]] — Claude-specific hook details for claude-translator
  • [[../../wiki/entities/sync-claude-md]] — v1 sync system being ported
  • agents.md standard — the cross-tool spec