Components

Components are bounded, named, re-renderable regions in a document — similar to web components or React components. They provide a way for agents and scripts to update specific sections of a document without touching the rest.

Syntax

Components use paired HTML comment markers:

<!-- agent:status -->
| Field | Value |
|-------|-------|
| build | passing |
<!-- /agent:status -->

The opening marker <!-- agent:NAME --> and closing marker <!-- /agent:NAME --> define the component boundary. Everything between the markers is the component's content.

Why paired markers?

A single marker (<!-- agent:x -->) has no boundary — after the first render inserts content, the next render can't distinguish the marker from rendered data. Paired markers create an unambiguous boundary. The closing marker makes re-rendering idempotent: agent-doc patch always knows exactly what to replace.

Naming rules

Component names must match [a-zA-Z0-9][a-zA-Z0-9-]* — start with alphanumeric, followed by alphanumerics or hyphens.

Valid: status, build-log, session2 Invalid: -start, _name, with spaces

Nesting

Components can nest. Inner components are parsed independently:

<!-- agent:dashboard -->
# System Overview

<!-- agent:status -->
All systems operational.
<!-- /agent:status -->

<!-- agent:metrics -->
CPU: 42%
<!-- /agent:metrics -->
<!-- /agent:dashboard -->

Patching status or metrics only affects that inner component. Patching dashboard replaces everything between its markers (including the inner components).

Patching components

The agent-doc patch command replaces a component's content:

# Replace from argument
agent-doc patch dashboard.md status "build: failing"

# Replace from stdin
echo "build: passing" | agent-doc patch dashboard.md status

# Replace from a script
curl -s https://api.example.com/status | agent-doc patch dashboard.md status

The markers are preserved — only the content between them changes.

Component configuration

Configure component behavior in .agent-doc/components.toml at the project root:

[log]
mode = "append"
timestamp = true
max_entries = 100

[status]
mode = "replace"       # default

[metrics]
pre_patch = "scripts/validate-metrics.sh"
post_patch = "scripts/notify-update.sh"

Modes

ModeBehavior
replaceFull content replacement (default)
appendNew content added at the bottom of existing content
prependNew content added at the top of existing content

Options

OptionTypeDefaultDescription
modestring"replace"Patch mode
timestampboolfalseAuto-prefix entries with ISO timestamp
max_entriesint0Auto-trim old entries in append/prepend modes (0 = unlimited)
pre_patchstringnoneShell command to transform content before patching
post_patchstringnoneShell command to run after patching (fire-and-forget)

Shell hooks

Hooks let you transform content or trigger side effects when a component is patched.

pre_patch

Runs before the content is written. Receives the new content on stdin, outputs transformed content on stdout:

[status]
pre_patch = "scripts/validate-status.sh"
#!/bin/bash
# scripts/validate-status.sh
# Transform content before it's written to the component

# Read incoming content from stdin
content=$(cat)

# Validate or transform
if echo "$content" | jq . > /dev/null 2>&1; then
    # Valid JSON — format it as a markdown table
    echo "$content" | jq -r 'to_entries[] | "| \(.key) | \(.value) |"'
else
    # Pass through unchanged
    echo "$content"
fi

Environment variables available:

  • COMPONENT — component name (e.g., status)
  • FILE — path to the document being patched

If the hook exits non-zero, the patch is aborted.

post_patch

Runs after the content is written. Fire-and-forget — output is inherited (prints to terminal), exit code is logged but doesn't affect the patch:

[metrics]
post_patch = "scripts/notify-update.sh"
#!/bin/bash
# scripts/notify-update.sh
echo "Component '$COMPONENT' updated in $FILE"
# Could trigger a webhook, send a notification, etc.

Components vs comments

Regular HTML comments are a user scratchpad — they're stripped during diff comparison and never trigger agent responses:

<!-- This is a regular comment — invisible to the agent -->

Component markers look like comments but are structural:

<!-- agent:status -->
This content is managed by the agent or scripts
<!-- /agent:status -->

The diff engine preserves component markers while stripping regular comments. This means:

  • Adding/removing regular comments does not trigger a response
  • Changing content inside a component does trigger a response
  • The markers themselves are never modified by patch