ADR 015: Provider Namespace Design
Status: Accepted Date: 2026-01-01
Context
Colin's providers are currently exposed to templates under the providers namespace (e.g., providers.mcp.github.resource()). This is verbose and bureaucratic. We also artificially elevate some providers like mcp and llm to the root namespace for convenience, creating inconsistency.
As we add more providers (file, S3, HTTP, future integrations like dbt/SQL), we need a clear, consistent namespace design that:
- Is concise and readable in templates
- Scales to many providers without collision
- Clearly indicates which methods are user-facing vs internal
- Handles providers that expose multiple methods (not just
read())
Decision
1. Use colin as the Provider Namespace
Replace providers with colin:
{# Before #}
{{ providers.mcp.github.resource('repo://...') }}
{{ providers.llm.extract(content, 'summary') }}
{{ providers.file.read('/path/to/file') }}
{# After #}
{{ colin.mcp.github.resource('repo://...') }}
{{ colin.llm.extract(content, 'summary') }}
{{ colin.file.read('/path/to/file') }}
Rationale:
- Self-explanatory and branded
- Reads naturally: "Colin reads file", "Colin connects to MCP"
- Shorter than
providers(5 chars vs 9) - More discoverable for new users than abstract names like
ctxorio
2. No Top-Level Shortcuts
With colin as the namespace, we no longer need to elevate providers to root level. colin.llm and colin.mcp are ergonomic enough:
{# These feel good as-is #}
{{ colin.llm.extract(content, 'summary') }}
{{ colin.mcp.github.resource('repo://...') }}
{{ colin.file.read('/data.json') }}
{{ colin.s3.read('s3://bucket/key') }}
This eliminates the inconsistency of having some providers at root and others nested.
3. Underscore Convention for Internal Methods
Provider methods follow Python's underscore convention:
method()- exposed to templates_method()- internal only, not exposed
class FileProvider(Provider):
async def read(self, path: str) -> str: # Exposed as colin.file.read()
async def exists(self, path: str) -> bool: # Exposed as colin.file.exists()
async def get_last_updated(self, path: str): # Exposed
async def _write(self, path: str, content: str): # Internal only
The template engine filters out underscore methods when exposing providers.
4. Utilities Outside the Namespace
The colin namespace is exclusively for providers. Utilities and other functions are registered as regular Jinja functions/filters outside the namespace:
{# Providers under colin #}
{{ colin.file.read('/path') }}
{{ colin.llm.extract(content, 'prompt') }}
{# Utilities as regular functions #}
{{ env('API_KEY') }}
{{ now() }}
{{ ref('other-doc') }}
Examples
Complete Template
---
name: Engineering Status
---
# Engineering Status
## GitHub Issues
{% set issues = colin.mcp.github.resource('repo://acme/platform/issues?state=open') %}
{{ issues.content }}
## S3 Data
{% set metrics = colin.s3.get('analytics/weekly-metrics.json') %}
{{ metrics | from_json | extract('key trends') }}
## Analysis
{% llm %}
Assess team velocity based on:
Issues: {{ issues.content }}
{% endllm %}
Consequences
providersnamespace renamed tocolin- No more artificial elevation of
mcp/llmto root - Underscore convention distinguishes internal vs template-exposed methods
- Utilities remain outside the
colinnamespace as regular functions
Migration
- Rename internal
providersnamespace tocolin - Update template engine to expose
colin.*instead ofproviders.* - Remove root-level
mcpandllmaliases - Update documentation and examples