Expand description
§Module: template
Template-mode support for in-place response documents. Parses structured patch blocks from agent responses and applies them to named component slots in the document, with boundary marker lifecycle management for CRDT-safe stream writes.
§Spec
parse_patches: Scans agent response text for<!-- patch:name -->...<!-- /patch:name -->blocks and returns a list ofPatchBlockvalues plus any unmatched text (content outside patch blocks). Markers inside fenced code blocks (``` or ~~~) and inline code spans are ignored so examples in responses are never mis-parsed as real patches.apply_patches: Delegates toapply_patches_with_overrideswith an empty override map. Applies parsed patches to matching<!-- agent:name -->components in the document.apply_patches_with_overrides: Core patch application pipeline:- Pre-patch: strips all existing boundary markers, inserts a fresh boundary at the end
of the
exchangecomponent (keyed to the file stem). - Applies each patch using mode resolution: stream overrides > inline attr (
patch=ormode=) >.agent-doc/config.toml ([components] section)> built-in defaults (exchange/findingsdefault toappend, all others toreplace). - For
appendmode, uses boundary-aware insertion when a boundary marker exists. - Patches targeting missing component names are routed as overflow to
exchange/output. - Unmatched text and overflow are merged and appended to
exchange/output(or an auto-createdexchangecomponent if none exists). - Post-patch: if the boundary was consumed, re-inserts a fresh one at the end of exchange.
- Pre-patch: strips all existing boundary markers, inserts a fresh boundary at the end
of the
reposition_boundary_to_end: Removes all boundaries and inserts a new one at the end ofexchange. Used by the IPC write path to keep boundary position current.reposition_boundary_to_end_with_summary: Same, with optional human-readable suffix on the boundary ID (e.g.a0cfeb34:agent-doc).template_info: Reads a document file, resolves its template mode flag, and returns a serializableTemplateInfowith per-component name, resolved mode, content, and line number. Used by editor plugins for rendering.is_template_mode(test-only): Legacy helper to detectmode = "template"string.
§Agentic Contracts
parse_patchesis pure and infallible for valid UTF-8; it returnsOkeven for empty or patch-free responses.- Patch markers inside fenced code blocks are never extracted as real patches. Agents may include example markers in code blocks without triggering unintended writes.
- Component patches are applied in reverse document order so earlier byte offsets remain valid throughout the operation.
- A boundary marker always exists at the end of
exchangeafterapply_patches_with_overridesreturns. Callers that perform incremental (CRDT/stream) writes may rely on this invariant. - Missing-component patches never cause errors; content is silently routed to
exchange/outputwith a diagnostic written to stderr. - Mode precedence is deterministic: stream override > inline attr (
patch=>mode=) >config.toml ([components] section)(patchkey >modekey) > built-in default. Callers can rely on this ordering when constructing overrides for stream mode. template_inforeads the document from disk; callers must ensure the file is flushed before calling (especially in the IPC write path).
§Evals
parse_single_patch: single patch block → onePatchBlock, empty unmatchedparse_multiple_patches: two sequential patch blocks → twoPatchBlocks in order, empty unmatchedparse_with_unmatched_content: text before and after patch block → unmatched contains both text segmentsparse_empty_response: empty string → zero patches, empty unmatchedparse_no_patches: plain text with no markers → zero patches, full text in unmatchedparse_patches_ignores_markers_in_fenced_code_block: agent:component markers inside ``` are preserved as contentparse_patches_ignores_patch_markers_in_fenced_code_block: nested patch markers inside ``` are not parsed as patchesparse_patches_ignores_markers_in_tilde_fence: patch markers inside ~~~ are ignoredparse_patches_ignores_closing_marker_in_code_block: closing marker inside code block is skipped; real close is foundparse_patches_normal_markers_still_work: sanity — two back-to-back patches parse correctlyapply_patches_replace: patch to non-exchange component replaces existing contentapply_patches_unmatched_creates_exchange: unmatched text auto-creates<!-- agent:exchange -->when absentapply_patches_unmatched_appends_to_existing_exchange: unmatched text appends to existing exchange; no duplicate componentrepair_duplicate_exchange_opener_merges_two_blocks: two complete exchange blocks → merged into onerepair_duplicate_exchange_opener_returns_none_for_single: single exchange block →None(no repair needed)apply_patches_post_patch_merges_duplicate_exchange_opener: post-patch guard merges duplicate exchange openers created during patchingapply_patches_missing_component_routes_to_exchange: patch targeting unknown component name appears in exchangeapply_patches_missing_component_creates_exchange: missing component + no exchange → auto-creates exchange with overflowinline_attr_mode_overrides_config:mode=replaceon tag wins overconfig.toml ([components] section)append configinline_attr_mode_overrides_default:mode=replaceon exchange wins over built-in append defaultno_inline_attr_falls_back_to_config: no inline attr →config.toml ([components] section)append config appliesno_inline_attr_no_config_falls_back_to_default: no attr, no config → exchange defaults to appendinline_patch_attr_overrides_config:patch=replaceon tag wins overconfig.toml ([components] section)append configtemplate_info_works: template-mode doc →TemplateInfo.template_mode = true, component list populatedtemplate_info_legacy_mode_works:response_mode: templatefrontmatter key recognizedtemplate_info_append_mode: non-template doc →template_mode = false, empty component listis_template_mode_detection:Some("template")→ true; other strings andNone→ false- (aspirational)
apply_patches_boundary_invariant: after any apply_patches call with an exchange component, a boundary marker exists at end of exchange - (aspirational)
reposition_boundary_removes_stale: multiple stale boundaries are reduced to exactly one at end of exchange
Structs§
- Component
Info - Per-component info for plugin rendering.
- Patch
Block - A parsed patch directive from an agent response.
- Template
Info - Template info output for plugins.
Functions§
- annotate_
exchange_ headings_ against_ baseline - apply_
patches_ pure - Apply patch blocks to a document’s components.
- apply_
patches_ with_ overrides_ pure - Apply patches with per-component mode overrides (e.g., stream mode forces “replace” for cumulative buffers even on append-mode components like exchange).
- deleted_
conversation_ tail_ cleanup - Return
afterwhen it is exactlybeforewith one safe escaped exchange tail outsideagent:exchangedeleted. - exchange_
baseline_ headings - Extract the set of stripped
### Re:heading lines from theexchangecomponent of a document. Used by the commit path to build a baseline of headings already present ingit HEADso the reposition step can mark all new-this-cycle headings (not just the last one) with(HEAD). - guard_
no_ conversation_ tail_ outside_ exchange - Fail closed when conversation content (prompts or response headings) would
land outside the live
<!-- agent:exchange -->block. This is a write-path guard — explicitagent-doc repairusesrepair_conversation_tail_outside_exchangeinstead. - guard_
no_ duplicate_ prompt_ residue_ outside_ exchange - Fail closed when prompt text already recorded in
agent:exchangestill appears as freeform Markdown after the exchange close marker. - normalize_
editor_ visible_ template_ structure - Normalize the structural template invariants required before an editor IPC client mutates the visible document.
- parse_
patches - Parse
<!-- patch:name -->...<!-- /patch:name -->blocks from an agent response. - remove_
duplicate_ answered_ exchange_ prompt_ tail - Remove a prompt tail after the latest exchange boundary when that tail is an exact duplicate of a prompt block already answered earlier in the exchange.
- remove_
post_ exchange_ duplicate_ prompt_ comments - remove_
post_ exchange_ duplicate_ prompt_ comments_ preserving - Like
remove_post_exchange_duplicate_prompt_comments, but keeps duplicate- looking comment lines that were already present inpreserve_doc. - remove_
post_ exchange_ duplicate_ prompt_ comments_ preserving_ docs - Like
remove_post_exchange_duplicate_prompt_comments_preserving, but accepts several ownership-proof documents and preserves the union of their ordinary post-exchange comment lines. - repair_
conversation_ tail_ outside_ exchange - Repair the safe malformed-template case where conversation content escaped
below
<!-- /agent:exchange -->and now trails the document after sibling components like pending/todo. - repair_
duplicate_ exchange_ close_ mixed_ scaffold_ tail - Repair the malformed-template case where a duplicated scaffold contains safe live conversation text before the stray second exchange close marker.
- repair_
duplicate_ exchange_ close_ scaffold - Repair the malformed-template case where a duplicate template scaffold is
inserted between two
<!-- /agent:exchange -->close markers. - repair_
duplicate_ exchange_ close_ tail - Repair the malformed-template case where a duplicate
<!-- /agent:exchange -->lands after escaped conversation content. - repair_
duplicate_ exchange_ opener - Merge duplicate
<!-- agent:exchange -->openers into a single exchange block. - repair_
prompt_ tail_ outside_ exchange - Repair only prompt-target drift that escaped below
<!-- /agent:exchange -->while leaving later markdown section separators outside the exchange block. - repair_
queue_ struck_ items_ escaped_ below_ marker - Remove struck queue items (
- ~~…~~) that drifted BELOW theagent:queueclosing marker into the inter-component parking lot (#queue-completed-items-escape-below-component). - reposition_
boundary_ to_ end - Reposition the boundary marker to the end of the exchange component.
- reposition_
boundary_ to_ end_ clean - Reposition the boundary marker without adding any
(HEAD)annotations. - reposition_
boundary_ to_ end_ clean_ with_ id - Reposition boundary using an explicit boundary ID when provided.
- reposition_
boundary_ to_ end_ clean_ with_ summary - Reposition boundary with an optional human-readable summary suffix, but do
not add any
(HEAD)heading annotations. - reposition_
boundary_ to_ end_ clean_ with_ summary_ and_ id - Like
reposition_boundary_to_end_clean_with_summarybut pins an explicit (deterministic) boundary ID instead of minting a fresh random one. The IPC patch builder seeds this from the stablepatch_idso a single write’s socket / file / fallback rebuilds all carry the same boundary, preventing the plugin from appending the response twice (#finalize-visible-buffer-ipc-timeout-race). - reposition_
boundary_ to_ end_ preserve_ head - Reposition boundary to end of exchange WITHOUT stripping
(HEAD)markers. - reposition_
boundary_ to_ end_ preserve_ head_ with_ id - Reposition boundary using an explicit ID, preserving
(HEAD)markers. - reposition_
boundary_ to_ end_ with_ baseline - Reposition boundary, with an optional set of baseline
### Re:headings (typically extracted from git HEAD). When a baseline is supplied, every### Re:heading in the current exchange whose normalized text is NOT in the baseline is treated as “new this cycle” and receives a(HEAD)suffix. Headings already present in the baseline are stripped of any stale(HEAD)suffix. When the baseline isNone, behavior matches the legacyannotate_latest_re_heading_with_headpath: only the last### Re:heading gets the marker. - reposition_
boundary_ to_ end_ with_ summary - Reposition boundary with an optional human-readable summary suffix.
- strip_
conversation_ tail_ outside_ exchange - Remove a safe escaped conversation tail below
<!-- /agent:exchange -->.