Skip to content

Proposal block formats

The agent uses three fenced markdown blocks to propose changes. Forge parses them out of chat messages, renders them as cards, and applies the changes when you approve.

For the asset-side companion (inline previews + click-to-open buttons that don’t require approval), see Forge-link blocks.

Memory proposal card titled "Proposed append to .forge/DECISIONS.md" with body content and Approve / Reject buttons

This page documents the wire formats. You don’t normally need to read this. It’s here for skill authors writing instructions for the agent, MCP integrators routing through Forge, and anyone debugging an unexpected card.

Used to add or replace content in .forge/*.md memory files (or ~/.forge/scratchpad/*.md in open mode).

```forge-propose
file: DECISIONS.md
operation: append
---
## 2026-04-30 Stamina over mana
Picked stamina to reinforce the survival pillar.
```

Fields:

  • file (required): path relative to the memory base directory. Cannot start with / or \ and cannot contain .. (path traversal is rejected).
  • operation (optional, default append): one of append or overwrite. Append adds to the end of the file with a separating blank line. Overwrite replaces the file’s content entirely.

The body after --- is the content to write. Markdown is preferred but plain text works.

Used to propose changes to source files. The agent specifies hunks with explicit line ranges, the file’s expected SHA-256, and a commit message for the auto-commit step.

```forge-propose-edit
file: src/components/Player.tsx
explanation: Add a debounce to the onChange handler
commit: feat(player): debounce input handler 250ms
base_sha256: 3a7c2d8e9f...64hex chars total...e1
---
@@ 12-12
import { useDebounce } from '@/lib/utils';
@@ 45-47
const debouncedSearch = useDebounce(value, 250);
useEffect(() => {
if (debouncedSearch) onSearch(debouncedSearch);
}, [debouncedSearch, onSearch]);
```

Fields:

  • file (required): path relative to the project root. Same path-traversal rejection rules as forge-propose.
  • explanation (optional): one-line summary shown on the card.
  • commit (optional, defaults to chore: edit <file>): the commit message used by auto-commit-on-approve. Conventional Commits style.
  • base_sha256 (required): the SHA-256 of the file’s contents when the agent read it, lowercase hex, exactly 64 characters. The apply step refuses to write if the file’s current hash doesn’t match.

Hunks are separated by @@ START-END markers:

  • @@ 12-12: replace line 12 with the lines that follow.
  • @@ 12-15: replace lines 12 through 15 (inclusive, 1-indexed) with what follows.
  • @@ 12: shorthand for @@ 12-12.
  • @@ 12-11: pure insertion before line 12 (start > end means an empty source range).

The replacement lines run until the next @@ ... marker or the end of the block.

Used to add a new auto-skill under ~/.forge/skills/auto/<name>/.

```forge-propose-skill
name: phaser-tilemap-collisions
---
---
name: phaser-tilemap-collisions
description: Phaser 3 tilemap collision setup with Arcade physics.
triggers:
- phaser tilemap
- tile collision
- arcade physics tilemap
engine: web
version: "1.0"
always_on: false
---
# Tilemap collisions in Phaser 3
...body...
```

Outer fields:

  • name (required): the skill slug. Becomes the directory name under auto/.

The body after the first --- is itself a complete skill file: YAML frontmatter, then ---, then markdown body. This is the same format as a curated skill (see Skills format).

Forge applies the three parsers in a stack so a message can contain any mix of them. The MessageList strippers run as:

stripEditProposals(stripSkillProposals(stripProposals(text)))

This removes the fenced blocks from the rendered message body so you see the agent’s prose alongside the cards rather than both the prose and the raw block syntax.

Forge silently ignores malformed blocks (missing required fields, bad SHA, path traversal, etc.) rather than rendering broken cards. If the agent emits something Forge doesn’t accept, the chat shows the agent’s text as if the block weren’t there. This is a deliberate fail-safe.

If you’re writing a skill that asks the agent to emit one of these blocks, double-check the formatting in your skill body. The agent follows skill instructions closely.