Request Transformers

Transform API requests before they are forwarded to the provider. Use transformers to edit XML-style tags (<system-reminder>, <aspy-context>, custom tags), inject context conditionally, or modify message content.

Quick Start

Add to your ~/.config/aspy/config.toml:

[transformers]
enabled = true  # Master switch - MUST be true for any transformer to run

[transformers.tag-editor]
enabled = true

# Remove reminders containing "debug" or "noisy"
[[transformers.tag-editor.rules]]
type = "remove"
tag = "system-reminder"
pattern = "debug|noisy"

# Add custom context (only on conversational turns, not during tool calls)
[[transformers.tag-editor.rules]]
type = "inject"
tag = "aspy-context"
content = "Custom context for Claude."
position = "end"
when = { has_tool_results = "=0" }

Architecture

Request → [Transformation Pipeline] → [Translation Pipeline] → Provider
              ↓
         TagEditor (XML tag manipulation)
         SystemEditor (system prompt modification)
         CompactEnhancer (compaction guidance)

Transformers run before translation, when the request is in known Anthropic format. This ensures consistent behavior regardless of target provider format.

Tag Editor

Edits any XML-style tags in user messages. Each rule explicitly specifies which tag it targets, allowing fine-grained control over different tag types.

Rule Types

Type Purpose Example
inject Add new tagged content Custom instructions, context
remove Delete tags matching a pattern Filter noise
replace Modify content within tags Update URLs

Rules are applied in order: Remove → Replace → Inject

The tag Field

Every rule must specify which XML tag it targets:

[[transformers.tag-editor.rules]]
type = "inject"
tag = "system-reminder"      # Creates <system-reminder>...</system-reminder>
content = "Your content here"

[[transformers.tag-editor.rules]]
type = "inject"
tag = "aspy-context"         # Creates <aspy-context>...</aspy-context>
content = "Different content"

[[transformers.tag-editor.rules]]
type = "remove"
tag = "noisy-tag"            # Only removes <noisy-tag> blocks
pattern = ".*"

Conditional Execution (when)

Rules can have conditions that must be met for the rule to apply:

[[transformers.tag-editor.rules]]
type = "inject"
tag = "aspy-context"
content = "Context info"
when = { has_tool_results = "=0", turn_number = ">1" }

Available Conditions

Condition Syntax Description
turn_number "=1", ">5", "<10", ">=3", "<=5", "every:3" Match conversation turn
has_tool_results "=0", ">0", ">3" Count of tool_result blocks in message
client_id "dev-1", "foundry\|local" Match client ID (pipe = OR)

Compound Conditions

Multiple conditions in the same when clause are ANDed together:

# Only fires on turn 1, with no tool results, for dev-1 client
when = { turn_number = "=1", has_tool_results = "=0", client_id = "dev-1" }

Frequency Control

Use every:N for periodic injection:

# Inject every 5th turn (5, 10, 15...)
when = { turn_number = "every:5" }

# Inject every 3rd conversational turn (no tool results)
when = { turn_number = "every:3", has_tool_results = "=0" }

Inject Rules

Add new tagged blocks:

[[transformers.tag-editor.rules]]
type = "inject"
tag = "system-reminder"
content = "Remember to use TypeScript for all code examples."
position = "end"  # Where to insert (default: end)

Position options:

Multiline content:

[[transformers.tag-editor.rules]]
type = "inject"
tag = "aspy-context"
content = """
Important context:
- Use TypeScript
- Follow project conventions
"""
position = "start"
when = { turn_number = "=1" }  # Only on first turn

Remove Rules

Delete tags whose content matches a regex pattern:

[[transformers.tag-editor.rules]]
type = "remove"
tag = "system-reminder"
pattern = "debug|verbose|noisy"  # Regex pattern

Conditional removal:

# Remove "Learning output style" reminder after turn 2
[[transformers.tag-editor.rules]]
type = "remove"
tag = "system-reminder"
pattern = "Learning output style"
when = { turn_number = ">2" }

Replace Rules

Modify content within matching tags:

[[transformers.tag-editor.rules]]
type = "replace"
tag = "system-reminder"
pattern = "old-api\\.example\\.com"  # Note: escape dots in regex
replacement = "new-api.example.com"

Supports regex capture groups:

[[transformers.tag-editor.rules]]
type = "replace"
tag = "config-tag"
pattern = "version: (\\d+)"
replacement = "version: 2 (was $1)"

Configuration Reference

Master Switch

[transformers]
enabled = true  # Required for any transformer to run

When enabled = false, the entire transformation pipeline is bypassed (zero overhead).

Tag Editor

[transformers.tag-editor]
enabled = true  # Enable this specific transformer

[[transformers.tag-editor.rules]]
type = "inject"
tag = "system-reminder"
content = "Your content"
position = "end"
when = { has_tool_results = "=0" }  # Optional conditions

Fail-Safe Guarantee

The transformation pipeline is designed to never break your requests:

  1. Errors don’t fail requests - If a transformer errors, the request continues with the original body
  2. One transformer failing ≠ pipeline fails - Other transformers still run
  3. Worst case = passthrough - Original unmodified request goes through

Startup Verification

Check if transformers are active in the startup output:

─ Pipeline ─
  ✓ transformers    Request editing

If disabled:

─ Pipeline ─
  ○ transformers    Request editing (disabled)

Observability

Transformation events are logged via tracing (not ProxyEvents):

DEBUG Request transformed by pipeline (pre-translation)
INFO  Request blocked by transformation pipeline: Content policy violation
WARN  Transformation error (continuing with original): ...

Set RUST_LOG=aspy::proxy::transformation=debug for detailed logs.

Use Cases

Inject Context Only During Conversation

[[transformers.tag-editor.rules]]
type = "inject"
tag = "aspy-context"
content = """
Aspy session active. Recovery tools available after /compact.
"""
when = { has_tool_results = "=0" }  # Skip during tool-heavy turns

First-Turn Orientation

[[transformers.tag-editor.rules]]
type = "inject"
tag = "system-reminder"
content = "This project uses Rust 2021 edition with async/await patterns."
when = { turn_number = "=1" }  # Only on first turn

Periodic Reminders

[[transformers.tag-editor.rules]]
type = "inject"
tag = "system-reminder"
content = "Remember to run tests before committing."
when = { turn_number = "every:10", has_tool_results = "=0" }

Filter Noisy Reminders

[[transformers.tag-editor.rules]]
type = "remove"
tag = "system-reminder"
pattern = "git status|codebase structure"

Client-Specific Rules

[[transformers.tag-editor.rules]]
type = "inject"
tag = "system-reminder"
content = "You are connected to the development environment."
when = { client_id = "dev-1|dev-2" }  # Pipe = OR

System Editor

Modifies the system field in Claude API requests. Use this to append, prepend, or replace content in system prompts—useful for adding global context, branding, or modifying Claude Code’s base behavior.

Rule Types

Type Purpose Example
append Add text to the end of the last system block Augmentation notices
prepend Add text to the beginning of the first system block Priority instructions
replace Find and replace text in all system blocks Update references

Rules are applied in order as defined in config.

Configuration

[transformers]
enabled = true

[transformers.system-editor]
enabled = true

# Append a notice to the system prompt
[[transformers.system-editor.rules]]
type = "append"
content = "\n\nYou are augmented by Aspy observability."

# Prepend priority instructions
[[transformers.system-editor.rules]]
type = "prepend"
content = "[ENHANCED MODE] "

# Replace references (regex supported)
[[transformers.system-editor.rules]]
type = "replace"
pattern = "Claude Code"
replacement = "Claude Code (Aspy-enhanced)"

Append and Prepend

Append adds content to the last text block in the system array:

[[transformers.system-editor.rules]]
type = "append"
content = """

## Aspy Active
Recovery tools available via aspy_recall.
"""

Prepend adds content to the first text block:

[[transformers.system-editor.rules]]
type = "prepend"
content = "[Session tracked by Aspy] "

Replace with Regex

The replace rule uses regex patterns and applies to all system blocks:

# Update version references
[[transformers.system-editor.rules]]
type = "replace"
pattern = "v\\d+\\.\\d+\\.\\d+"
replacement = "v2.0.0"

# Capture groups work
[[transformers.system-editor.rules]]
type = "replace"
pattern = "(Claude) (Code)"
replacement = "$1 $2 (enhanced)"

String vs Array System Format

The System Editor handles both system prompt formats:

String format is automatically converted to array format when modified, then the rules are applied.

Use Cases

Add global context:

[[transformers.system-editor.rules]]
type = "append"
content = "\n\nThis project uses Rust 2021 edition with async/await patterns."

Branding/identification:

[[transformers.system-editor.rules]]
type = "prepend"
content = "[MyCompany Dev Environment] "

Update outdated references:

[[transformers.system-editor.rules]]
type = "replace"
pattern = "api\\.old-domain\\.com"
replacement = "api.new-domain.com"

Compact Enhancer

Detects Anthropic’s compaction prompts and enhances them with continuity guidance. When Claude Code’s context window fills up, Anthropic sends a special prompt asking Claude to summarize the conversation. This transformer appends instructions to improve what gets preserved.

How It Works

  1. Detection - Multi-signal detection identifies compaction requests:
    • Primary signal: “summary of the conversation” phrase (required)
    • Structural markers: Section headers like “Primary Request”, “Pending Tasks” (2+ required)
  2. Injection - Appends continuity guidance to the compaction prompt:
    • Prompts for active work tracks, key decisions, current mental model
    • Suggests searchable keywords for post-compaction recovery
    • Mentions aspy_recall for context lookup

Configuration

[transformers]
enabled = true

[transformers.compact-enhancer]
enabled = true

What Gets Injected

When a compaction request is detected, this text is appended:

## Aspy Continuity Enhancement

**For the summary:** To help the continuing Claude maintain flow, please include:
- **Active Work Tracks:** What features/bugs/tasks are in progress (with file paths if relevant)
- **Key Decisions Made:** Important choices that shouldn't be revisited
- **Current Mental Model:** The user's goals and approach being taken

**Post-compaction recovery:** The continuing Claude has `aspy_recall` to search
the full pre-compaction conversation. Include 3-5 searchable keywords (feature names, concepts,
file paths) that would help locate detailed context.

Why This Matters

Without guidance, compaction summaries often lose:

The CompactEnhancer nudges the summarizing Claude to preserve what the continuing Claude actually needs.


Future Transformers

Planned additions:

Future Conditions

Planned when conditions:

Implementation Notes