Skip to content

Section Fencing & Safe Merges

When to Use This Guide

Read this guide if you:

  • Are writing or modifying agent templates
  • Want to run --update --merge and need to understand what will and won't change
  • Are troubleshooting a merge that overwrote user-authored content (or failed to update stale generated content)
  • Are adding project-specific agent routing or workflow extensions to a generated team

What Section Fencing Is

When AgentTeams generates an agent file from a template, some regions of that file are owned by the module (rendered from templates and placeholder resolution) and some regions should remain under your control (project-specific routing rows, workflow extensions, team policy additions).

Without explicit boundaries, re-generation would either overwrite everything or be unable to safely update anything. Section fences solve this with machine-readable HTML comment markers that survive Markdown rendering invisibly.


Fence Marker Syntax

<!-- AGENTTEAMS:BEGIN section_id v=1 -->
...template-generated content...
<!-- AGENTTEAMS:END section_id -->

Rules:

  • The opening tag must exactly match <!-- AGENTTEAMS:BEGIN <section_id> v=<version> --> — no spaces inside -->.
  • section_id is lower_snake_case, unique within a file.
  • v=<version> is an integer starting at 1. The merge engine accepts any value; the field is reserved for future migration tooling.
  • The END marker carries only section_id — no version attribute.
  • Markers are HTML comments and are invisible in Markdown previews and rendered documentation.
  • Fenced regions must not be nested.

FENCED vs USER-EDITABLE Designations

Every template that uses fences must include a section manifest immediately after the YAML front matter (if present):

<!--
SECTION MANIFEST
| section_id          | designation   |
|---------------------|---------------|
| routing_table_rows  | FENCED        |
| available_workflows | FENCED        |
| authority_hierarchy | FENCED        |
| constitutional_rules| USER-EDITABLE |
-->
Designation Meaning
FENCED Module-owned. Replaced on every --merge run.
USER-EDITABLE Your content. Never touched by --merge.

The USER-EDITABLE Gap

For sections like Domain Agent Routing, the template generates a base set of rows (fenced), but you may need to add project-specific rows below the fence. AgentTeams templates intentionally leave a gap between fenced and user-editable regions:

## Domain Agent Routing

| Content Area | Agent | Key Indicators |
|---|---|---|
<!-- AGENTTEAMS:BEGIN routing_table_rows v=1 -->
| Primary deliverables | `@primary-producer` | New work in `src/` |
<!-- AGENTTEAMS:END routing_table_rows -->
> ⚙️ **Project-specific rules and extension points go here.** This section is USER-EDITABLE.

When --merge runs, it replaces only the routing_table_rows fenced block. Anything you added after the END marker — including the entire USER-EDITABLE gap below — is preserved untouched.


Partial Fencing

A fence may bracket only a subset of a section. For example, a routing table may have a template-generated row set but a user-editable header and footer:

## My Custom Section

*(Header: user-authored, not fenced)*

<!-- AGENTTEAMS:BEGIN my_custom_rows v=1 -->
| Row generated from template | ... |
<!-- AGENTTEAMS:END my_custom_rows -->

*(Footer: user-authored, not fenced)*

Only the content between the markers is replaced on merge. The header and footer survive.


How --update --merge Uses Fences

When you run:

agentteams --description brief.json --project /path --framework copilot-vscode --update --merge

The merge engine:

  1. Reads the latest template for each agent
  2. Renders the fresh placeholder-resolved content for every FENCED section
  3. In each output file: locates the matching BEGIN/END markers, replaces only that block with fresh content
  4. Leaves all content outside fenced regions untouched
  5. Writes backup files before any change (unless --no-backup is set)
  6. Appends newly introduced fenced sections when the target file is already fence-managed and missing those sections

Auto-fence on update (default)

By default, --update --merge (with --yes) auto-retrofits a content fence onto legacy (unfenced) files so their template region becomes mergeable on the same run instead of being skipped. Each retrofit is backed up first and the shrink-guard still suppresses material template shrinks, so the legacy body is recoverable. Pass --no-add-fence-markers to opt out and keep the conservative skip-legacy behaviour described below:

Files with no fence markers at all are treated as legacy/unmanaged for merge. With --no-add-fence-markers, --merge skips the file and reports a merge error for that path (except select machine-managed file exceptions).


Adding Fences to a Template

If you are authoring a new template:

  1. Identify which sections in your template are module-owned (should regenerate on every --merge)
  2. Identify which sections are project-specific (should survive --merge)
  3. Wrap module-owned sections in BEGIN/END markers
  4. Add a section manifest at the top of the file
  5. Mark project-specific regions as USER-EDITABLE in the manifest

For detailed registration steps, see the Template Authoring Guide.


Configuration

No CLI flags are required for fencing to work — it is always active when --merge is used.

Flag Effect
--merge Enable merge mode (required for fence-based selective update)
--update Fetch latest templates before merge
--no-backup Skip backup file creation before writes (not recommended for production)
--dry-run Show what would change without writing any files

Best Practices

  • Always --dry-run first when running --update --merge on a team that has significant user-authored content. Review the diff before committing.
  • Add your project-specific rows in the USER-EDITABLE gap, not inside fenced blocks. Content inside a fenced block will be overwritten on the next merge.
  • Keep section manifests accurate. If you add a new fenced section to a template, update the manifest. Merge behavior is driven by AGENTTEAMS:BEGIN/END fence markers; manifests are governance metadata for template authors and reviewers.
  • Version-bump the v= attribute when making a breaking change to a fenced section's content contract (changing the column structure of a table, for example). This signals to reviewers that a migration may be needed.

Troubleshooting

Merge overwrote my custom content

Cause: Your content was placed inside a FENCED block.

Fix: Move custom content to below the <!-- AGENTTEAMS:END ... --> marker, into the USER-EDITABLE gap.

Recovery (W22 data-loss safety net): The default policy is --shrink-policy=preserve: a fence whose new render is materially smaller than the existing body is not rewritten — the enriched body is kept, the shrink is suppressed, and a Notice is emitted. Nothing is lost and no sidecar is produced (non-shrinking fences in the same file still update normally). Under --shrink-policy=warn the smaller body is written, and as a safety net the full pre-merge body of every fence that triggered a shrink notice is persisted to a sidecar at <output_dir>/.agentteams-backups/<ts>/<rel_path>.lost.<sid>.md.agentteams-bak, with the Notice annotated — recovery: <sidecar-path>; to recover, copy the lost text from the sidecar into the USER-EDITABLE gap below the fence, then re-run. (halt aborts on a shrink; allow writes the smaller body silently with no sidecar.)

Two fence ids are exempt from the shrink heuristic and never produce sidecars: threat_intelligence and threat_data. Those are refreshed each run from live CISA KEV / NVD / OSV feeds; CVE rotation is expected. The canonical history for live-feed fences is the cache JSON (references/security-vulnerability-watch.json).

Merge left stale generated content

Cause: The output file was created before fence markers were introduced (pre-fencing era file). It has no markers to match.

Fix: Run --migrate to add pre-fencing snapshots and prepare the file for fence-based updates. See the Migration Guide.

"WARN: fenced section found in template but absent from output file"

Cause: The template was updated to add a new fenced section, but the output file predates that section.

Fix: Review the diff. In current merge behavior, new fenced sections are appended when the output file already has valid fence markers. If the file has no fences at all, the default --update --merge --yes auto-retrofits a content fence on the same run (see Auto-fence on update); pass --no-add-fence-markers to opt out, in which case use --overwrite or run --migrate first.