ADR 005: ref() Returns Resource Objects
Status: Accepted Date: 2024-12-27 Updated: 2026-01-01
Context
The original design was vague about what ref() returns, simply saying "returns content." But documents have metadata beyond just content:
- Name and description (from frontmatter)
- A way to re-fetch for staleness checking
- Version information for change detection
Decision
ref() returns Resource objects. The specific type depends on the input:
For string paths (project refs):
class ProjectResource(Resource):
path: str # Relative path (e.g., "greeting.md")
name: str # From frontmatter or derived from path
description: str | None # From frontmatter
# Inherited from Resource:
# .content: str # Compiled output
# .ref() -> Ref # For re-fetching
# .version: str # Content hash
def __str__(self) -> str:
return self.content
For provider resources (S3, MCP, HTTP, etc.):
# ref() returns the same Resource type, unchanged
ref(colin.s3.prod.get("config.json")) # Returns S3Resource
ref(colin.mcp.github.resource("...")) # Returns MCPResource
Usage:
{{ ref("context/foo") }} {# Outputs content via __str__ #}
{{ ref("context/foo").name }} {# Access name #}
{{ ref("context/foo").version }} {# Access version #}
{% set cfg = ref(colin.s3.prod.get("config.json")) %}
{{ cfg.content }} {# S3 content #}
Rationale
- Rich metadata: Templates can access more than just content
- Ergonomic:
__str__means{{ ref() }}works naturally in templates - Unified model: All refs are Resources with
.ref()for staleness tracking - Type safe: Specific Resource subclasses for each provider
See ADR 017 for the full Resource/Ref architecture.
Consequences
ref()returnsProjectResourcefor string pathsref()passes through provider Resources unchanged (just tracks them)- All Resources have
.content,.ref(),.version - Staleness checking uses
resource.ref()to replay and compare versions