Skip to content
Documentation GitHub
Development Guides

Adding a DSPy Template

Guide for adding a new shipped template to the Python sidecar’s template registry.

Overview

Templates are DSPy module wrappers that ship with the sidecar binary. Each template has a template_id, a default signature, and a description. The Rust agent harness references templates by ID — it never sends Python source code.

Prerequisites

  • Python sidecar dev environment set up (cd apps/python-sidecar && uv sync --group dev)
  • Familiarity with DSPy modules (dspy.Predict, dspy.ChainOfThought, etc.)
  • Understanding of the template-only execution model (see the Skill System documentation)

Steps

1. Create the template module

Create a new file in apps/python-sidecar/src/inklings/dspy/templates/:

"""My Template -- brief description of what it does."""
from __future__ import annotations
import dspy
from inklings.dspy.templates.base import BaseTemplate
class MyTemplate(BaseTemplate):
template_id: str = "my_template"
description: str = "Brief description of what this template does"
def __init__(self, signature: str = "input -> output") -> None:
super().__init__(signature)
self.module: dspy.Predict = dspy.Predict(signature)

Key requirements:

  • Extend BaseTemplate — provides forward(), save_state(), and load_state() for free
  • Set template_id — must match the registry key (snake_case)
  • Set description — shown in the manifest response
  • Set self.module — must be a dspy.Module instance
  • Accept a signature parameter — allows the harness to customize input/output fields

2. Override forward() if needed

The default forward() in BaseTemplate calls self.module(**kwargs) and converts the result to a dict. Override it only if your template needs custom pre/post-processing:

def forward(self, **kwargs: Any) -> dict[str, Any]:
# Custom logic before the DSPy call
enriched = {"input": kwargs["input"], "context": "additional context"}
result = self.module(**enriched)
return self._result_to_dict(result)

3. Register in the template registry

Open apps/python-sidecar/src/inklings/dspy/templates/registry.py and add an entry to the TEMPLATES dict:

"my_template": {
"class": "MyTemplate",
"module_path": "inklings.dspy.templates.my_template",
"signature": "input -> output",
"description": "Brief description of what this template does",
"parameters": {},
},

Fields:

FieldPurpose
classPython class name to import
module_pathDotted import path to the module file
signatureDefault DSPy signature (can be overridden at execution time)
descriptionHuman-readable description (shown in manifest)
parametersOptional template-specific parameters with type, default, and description

If your template accepts extra parameters (like num_chains or max_iterations), declare them:

"parameters": {
"max_iterations": {
"type": "int",
"default": 3,
"description": "Maximum refinement iterations",
},
},

4. Write tests

Create apps/python-sidecar/tests/test_my_template.py:

from unittest.mock import MagicMock, patch
from inklings.dspy.templates.my_template import MyTemplate
def test_my_template_initializes():
t = MyTemplate(signature="question -> answer")
assert t.template_id == "my_template"
assert t.module is not None
def test_my_template_forward(mock_prediction):
t = MyTemplate()
with patch.object(t.module, "__call__", return_value=mock_prediction):
result = t.forward(input="test")
assert "output" in result
def test_my_template_state_roundtrip(fake_save_factory):
t = MyTemplate()
t.module.save = fake_save_factory({"test": "state"})
state = t.save_state()
assert state == {"test": "state"}

Use the shared fixtures from conftest.py (mock_prediction, fake_save_factory) for mocking DSPy internals.

Also add your template to test_all_templates.py — it parametrizes across all registered templates to verify basic instantiation and manifest inclusion.

5. Verify the manifest

Run the manifest test to confirm your template appears:

Terminal window
uv run pytest tests/test_manifest.py -v

The manifest handler reads from the TEMPLATES registry and returns metadata for all templates. Your new template should appear in the response.

6. Run the full suite

Terminal window
uv run pytest
uv run ruff check .
uv run mypy src/

All tests should pass and coverage should remain above 80%.

Existing Templates

Template IDDSPy ModuleDescription
predictdspy.PredictBasic single LLM call with signature
chain_of_thoughtdspy.ChainOfThoughtStep-by-step reasoning before output
reactdspy.ReActInterleaved reasoning and action steps
multi_chain_comparisondspy.MultiChainComparisonGenerate and compare multiple reasoning chains
best_of_nCustomRun N completions and select the best
refineCustomIteratively improve output with self-critique
parallelCustomRun multiple sub-modules and aggregate results
retrievedspy.Retrieve + dspy.PredictRetrieval-augmented generation with context injection
program_of_thoughtdspy.ProgramOfThoughtGenerate and execute code (requires sandbox)
code_actdspy.CodeActAgent with code execution (requires sandbox)

Architecture Notes

  • Template-only execution: The sidecar only runs shipped templates. dspy_module skill artifacts store a template_id + state_blob (serialized DSPy JSON state), never source code.
  • State serialization: save_state() / load_state() use DSPy’s native JSON serialization via a temp file. The state blob is opaque to the Rust side.
  • LLM routing: Templates never make direct API calls. The InklingsLM adapter intercepts DSPy’s LLM calls and routes them back to Rust via JSON-RPC IPC, where the ProviderRegistry handles actual API requests.

See Also

Was this page helpful?