ADR 022: Output Function for Self-Referencing
Status: Accepted Date: 2026-01-22
Context
Documents needed a way to access their own previous output to support workflows where:
- Users manually edit published output files and want those edits preserved on recompilation
- LLM blocks need to make targeted edits rather than rewriting from scratch
- Documents evolve over time rather than being regenerated completely
The existing previous_rendered variable in defer blocks read from Colin's artifact cache, which didn't capture manual edits to published files. It was also only available in defer blocks, limiting its usefulness.
Decision
Introduce a unified output() function available everywhere in templates:
{{ output() }} # reads from published output (output/)
{{ output(cached=True) }} # reads from artifact cache (.colin/compiled/)
Key behaviors:
- Returns
RenderedOutputwith.contentand.sections, orNoneif not found - Does NOT contribute to staleness tracking (avoids infinite loops)
- Works in all template contexts, not just defer blocks
output()falls back to cached if published file doesn't existprevious_renderedis removed (breaking change)
Implementation Details
The function reads from the document's output path as recorded in the manifest:
output()(default): Reads from{output_path}/{doc.output_path}. If the published file doesn't exist (first compile, private doc, deleted file), falls back to the cached version.output(cached=True): Reads from.colin/compiled/{doc.output_path}. No fallback—returnsNoneif not found.
This requires that the document has been compiled at least once (so the manifest has output_path). On first compile, both return None.
Human-in-the-Loop Workflow
The core use case is enabling collaboration between Colin and humans on the same document:
┌─────────────────────────────────────────────────────────────┐
│ 1. colin run │
│ └─> Compiles model → writes to output/doc.md │
│ │
│ 2. User edits output/doc.md directly │
│ └─> Fixes typo, adds paragraph, adjusts wording │
│ │
│ 3. colin run (triggered by source change) │
│ └─> Template calls output() → sees user's edits │
│ └─> LLM makes targeted updates, preserving edits │
│ └─> Writes new output that includes user changes │
└─────────────────────────────────────────────────────────────┘
This works because output() reads the actual file on disk, not what's recorded in the manifest. User edits are captured without any manifest update—Colin sees the current filesystem state.
Example: Docs That Evolve
---
name: Feature Documentation
---
{% set current = output() %}
{% if current %}
{# Preserve existing content, make targeted updates #}
{% llm %}
Here is the current documentation:
{{ current.content }}
Here is the latest source code:
{{ ref(file.get("src/feature.py")) }}
Update the documentation to reflect any changes in the source.
Make minimal edits. Preserve existing structure and wording.
{% endllm %}
{% else %}
{# First compile: generate from scratch #}
{% llm %}
Write documentation for this feature:
{{ ref(file.get("src/feature.py")) }}
{% endllm %}
{% endif %}
On subsequent compiles, user edits to the output file are loaded, passed to the LLM, and preserved in the updated output.
Rationale
- Unified access: One function replaces the defer-only
previous_renderedwith broader availability - Two sources, one interface: The
cachedkwarg cleanly distinguishes between "what Colin produced" and "what's on disk now" - Preserves manual edits: Default behavior reads the published file, capturing any manual changes without manifest updates
- Graceful fallback: Falls back to cached if published doesn't exist, handling first compile and private docs
- No staleness loops: Reading own output doesn't create a self-dependency that would force constant recompilation
Consequences
- Breaking change:
previous_renderedremoved from defer blocks CompileContextrequiresconfigandoutput_formatparameters- Documents can implement "edit-in-place" workflows where manual changes persist
- The mental model shifts from "Colin owns the output" to "Colin collaborates on the output"
- User edits become first-class inputs to the compilation process