Skip to content

Generation Pipeline

This page documents the internals of the AgentsMesh generation and import pipelines.

Generation pipeline

When you run agentsmesh generate, the CLI executes 7 ordered steps:

Step 1: Load config

Parse agentsmesh.yaml, validate the schema, then merge agentsmesh.local.yaml on top. Local overrides narrow targets and features but cannot expand them.

Step 2: Load canonical sources

Read all files from .agentsmesh/:

  • Rules from rules/*.md
  • Commands from commands/*.md
  • Agents from agents/*.md
  • Skills from skills/*/SKILL.md + supporting files
  • MCP from mcp.json
  • Permissions from permissions.yaml
  • Hooks from hooks.yaml
  • Ignore patterns from ignore

Then resolve extends entries — fetch from ~/.agentsmesh/cache/ or remote if not cached. Load installed packs from .agentsmesh/packs/. Merge everything: local wins over packs, packs win over extends.

Step 3: Generate per target

For each enabled target, run target-specific generators:

Canonical config
│
├──► Claude Code generator ──► {path, content}[]
├──► Cursor generator ──► {path, content}[]
├──► Copilot generator ──► {path, content}[]
└──► ...

Each generator produces an array of {path, content} pairs in the target’s native format. For example, the Cursor generator converts rules to .mdc format, the Claude Code generator writes settings.json for permissions/hooks/MCP.

When a target needs to fold canonical content into a larger instruction file, the generator writes an AgentsMesh managed block instead of ad hoc inline text. Managed blocks are the shared marker contract that prevents duplicate appends and lets import restore embedded content.

Step 4: Rewrite references

Internal file references are rewritten from canonical paths to target-relative paths:

.agentsmesh/skills/api-gen/template.hbs
→ .claude/skills/api-gen/template.hbs (for Claude Code)
→ .cursor/skills/api-gen/template.hbs (for Cursor)

This keeps cross-file links valid in each tool’s native directory.

A root instruction file that two enabled targets both write (for example AGENTS.md for Codex CLI and Cursor) keeps canonical references instead, so both copies are identical and merge into one file. Its Markdown links are still rebased: [TS rule](./typescript.md) in .agentsmesh/rules/_root.md becomes [TS rule](.agentsmesh/rules/typescript.md) in AGENTS.md, a file that exists for every target.

Generated-only managed blocks and canonical embedded payloads are protected from rewriting when their contents must stay literal. So are URLs — including a URL whose path contains parentheses, such as a Next.js route group (apps/(marketing)/package.json).

Generated Markdown is then checked for broken local links. Three shapes are deliberately not treated as links, so ordinary prose never fails a run: link syntax quoted inside inline code (`[label](path.md)`, which is how a rule documents the syntax), GFM footnote definitions ([^1]: prose), and anything inside a fenced code block. Note that a path written inside backticks is still rebased — only the broken-link check skips it.

Step 5: Resolve collisions

Detect overlapping output paths across features. When two features would write to the same file path, the resolution priority is:

  1. Native feature output (highest priority)
  2. Embedded/projected output (lower priority — deduplicated away)

Step 6: Write output

Create target directories if they don’t exist. Write all {path, content} pairs to disk. Update .agentsmesh/.lock with canonical-source checksums and an outputs map of every generated file’s checksum (so agentsmesh check can later detect direct edits to generated files).

Content is not always written verbatim. Every emitted output — including scopeExtras output — passes through the shared merge policy first:

  1. The target descriptor’s mergeGeneratedOutputContent hook is consulted for the resolved path. Targets use it to own only their own keys in a shared file (for example the mcpServers key of a config the tool’s own UI also writes).
  2. When the descriptor declines the path, a settings.json fallback merges the generated keys into the file already on disk.
  3. When the file on disk is comment-bearing JSONC (or otherwise cannot be parsed), it is left completely untouched and reported as unchanged rather than replaced. Losing a file to one // line is never the right trade.

That is why user-owned keys in co-owned config files — ~/.claude/settings.json, ~/.claude.json, crush.json, .zed/settings.json, ~/.continue/config.yaml and their siblings — survive regeneration.

Step 7: Clean stale files

Build the candidate set from every active target’s managedOutputs: the declared files plus a recursive walk of the declared dirs. Any candidate that the current run did not emit is deleted. This prevents stale config from accumulating, and it is how revocation works — dropping a rule from canonical removes its generated file.

Two paths are never deleted:

  • Any static managedOutputs.files entry the lock has no provenance for. AgentsMesh only evicts what it wrote; a hand-authored AGENTS.md on a first run stays.
  • Anything in managedOutputs.coOwnedFiles. AgentsMesh writes into these files but the user owns them too, so a run that emits nothing for one must leave it alone. The co-owned set is collected across all active targets before the sweep, so one target’s directory walk cannot reach a file another target co-owns.
  • Anything the current run emitted, which is the expected-path set.

The one exception is managedOutputs.supersededFiles: alternate instruction locations (a pre-migration .claude/CLAUDE.md) that are removed whenever a run emits the primary root instruction, so a tool never loads the same rules twice. Plugin descriptors are read the same way as built-in ones, so a plugin declaring coOwnedFiles or supersededFiles gets the same treatment.


Import pipeline

agentsmesh import --from <target> runs the generation pipeline in reverse:

Step 1: Read tool-specific files

Read the tool’s native config format from its directory.

Step 2: Parse tool format

Convert tool-native format to an intermediate canonical representation:

  • .cursor/rules/*.mdc → parse Cursor frontmatter, strip Cursor-specific fields
  • .claude/settings.json → extract mcpServers, permissions, hooks
  • AGENTS.md → parse embedded sections for rules, commands, agents, skills, MCP

Step 3: Read embedded metadata

For projected/embedded features, read the AgentsMesh metadata comments to restore original canonical form. For example, a Codex CLI command embedded as a skill is restored to commands/*.md format.

For aggregate instruction files, managed blocks such as agentsmesh:embedded-rules are split back into their original canonical files. See Managed Embedding for the marker format and round-trip rules.

Step 4: Rewrite references

Tool-relative file paths are rewritten to canonical paths:

.claude/skills/api-gen/template.hbs
→ .agentsmesh/skills/api-gen/template.hbs

Step 5: Write canonical files

Write the restored canonical files to .agentsmesh/. Existing files are merged — no duplicates, no overwrites without explicit confirmation.


Lock file

.agentsmesh/.lock is a YAML file managed by the CLI:

generated_at: 2026-03-28T10:00:00Z
generated_by: alice
lib_version: 0.30.1
checksums:
rules/_root.md: sha256:abc123...
rules/security.md: sha256:def456...
extends: {}
packs: {}
outputs:
.claude/rules/_root.md: sha256:ghi789...
.cursor/rules/_root.mdc: sha256:jkl012...
AGENTS.md: sha256:mno345...
  • checksums — hashes of the canonical .agentsmesh/ sources (paths relative to .agentsmesh/).
  • outputs — hashes of every generated file written or verified at generate time (paths relative to the project root, forward-slashed).
  • stale_targets — only present after agentsmesh generate --targets … left out an enabled target when the canonical sources had changed. Those targets’ files were not regenerated from the checksums above, so check fails until they are generated again. A full agentsmesh generate removes the key.

agentsmesh check reads this file and re-hashes both maps against what’s currently on disk. A canonical mismatch or a changed/deleted generated output is reported as drift.

agentsmesh generate rewrites the lock only when checksums, extends, packs, outputs or stale_targets change. A run that changes nothing leaves the file byte-for-byte as it was, so your git tree stays clean after generate, and a teammate’s run doesn’t create a lock diff. generated_at, generated_by and lib_version therefore describe the last run that changed the lock, not the latest run.

Filtered generates (--targets/--features) merge their outputs per-path into the existing outputs map without pruning stale entries; a full agentsmesh generate replaces the map, which is when entries for disabled targets drop off. agentsmesh merge keeps the outputs map: it combines the entries of both branches, and where both branches recorded the same file, your branch’s hash wins. That hash can be out of date for a file the other branch changed, so until you run agentsmesh generate, check can report that file as modified. Only when neither branch’s lock had an outputs map does the merged lock have none, and then check skips output verification until you regenerate.

The lock file is committed to git. It should not be edited manually.