---
description: When we are creating prompts that will be used by an LLM for agents
alwaysApply: false
version: 1.0.0
---

# Prompt Engineering Best Practices for LLM-to-LLM Communication

When creating prompts that will be read and executed by other LLMs (commands, workflows,
agent prompts), follow these practices. These guidelines are for prompts that LLMs write
for other LLMs to consume - not for human-to-LLM interaction.

## Why This Document Minimizes Formatting

This document is designed to be read by LLMs, not humans. Therefore:

- Minimal markdown formatting: No excessive bold, italics, or decorative symbols. These
  waste tokens and add no semantic value for LLM comprehension.
- Minimal "bad" examples: LLMs encode patterns from what they see, regardless of labels
  like "wrong" or "don't do this." Showing anti-patterns teaches the LLM to reproduce
  them.
- Simple structure: Headings for organization, code blocks for actual patterns, plain
  text for instructions.
- Clear over clever: Direct language that LLMs can parse literally, not stylistic
  variations.

When you read "avoid X" or see a counterexample in this document, understand that we're
violating our own principle for teaching purposes - but minimize this pattern in prompts
you create for LLM consumption.

## Key Principles for LLM-Readable Prompts

- Assume the executing model is smarter: The model executing your prompt is likely more
  capable than the model that created it. Trust its abilities rather than
  over-prescribing implementation details.
- Front-load critical information: LLMs give more weight to early content
- Be explicit: LLMs can't infer context the way humans do
- Maintain consistency: Use the same terminology throughout
- Structure matters: Clear boundaries (especially XML tags) help LLMs parse complex
  prompts
- Examples teach patterns: What you show is what the LLM will do
- Clarity over brevity: Never sacrifice unambiguous interpretation for token savings
- Explain motivation: Tell the LLM _why_ a constraint exists - it generalizes from
  reasoning better than from bare rules
- Descriptive over directive: "Use this tool when modifying files" works better than
  "CRITICAL: You MUST use this tool" - aggressive language can cause over-triggering
- Positive framing: "Write in flowing prose" is clearer than "Don't use markdown" -
  positive instructions are unambiguous, negative ones require constructing then
  negating

## Pattern Reinforcement Through Examples

When creating prompts for other LLMs to execute, pattern teaching becomes critical. LLMs
learn from what you show them, not from what you tell them to avoid.

### How LLMs Process Examples in Prompts

When an LLM reads a prompt file with examples, it encodes those patterns for
reproduction:

1. Pattern Matching Over Labels: LLMs reproduce structural patterns. Code structure
   creates strong activation. Text labels like "wrong" or "don't do this" are weak
   signals that don't override pattern encoding.

2. Direct Teaching Through Examples: When writing command workflows or agent prompts,
   you're teaching the executing LLM. Every example shown is a lesson it will follow.

3. Consistency is Critical: In LLM-to-LLM communication, inconsistent examples cause
   unpredictable behavior. The executing LLM can't resolve ambiguity the way humans do.

4. Attention Weighting: All tokens in the prompt receive attention. Structural patterns
   activate high attention regardless of surrounding text saying "avoid this."

### Writing Effective Instructions for LLM Execution

For command workflows and agent prompts:

1. Flood with correct patterns: Show 5+ examples of the standard approach
2. Never show anti-patterns: Don't include "wrong" examples - the LLM will reproduce
   them
3. Describe exceptions in prose: If there are edge cases, describe them in words, not
   code
4. Maintain pattern consistency: All examples should follow the same structure

Example of good pattern teaching in a prompt file:

```xml
<task>
Update all API endpoints to use consistent error handling
</task>

<examples>
// Pattern to follow:
async function getUser(id) {
  try {
    const user = await db.users.findById(id);
    if (!user) {
      throw new NotFoundError('User not found');
    }
    return user;
  } catch (error) {
    logger.error('Failed to get user', { id, error });
    throw error;
  }
}

async function updateUser(id, data) {
  try {
    const user = await db.users.update(id, data);
    if (!user) {
      throw new NotFoundError('User not found');
    }
    return user;
  } catch (error) {
    logger.error('Failed to update user', { id, error });
    throw error;
  }
}

async function deleteUser(id) {
  try {
    const result = await db.users.delete(id);
    if (!result) {
      throw new NotFoundError('User not found');
    }
    return result;
  } catch (error) {
    logger.error('Failed to delete user', { id, error });
    throw error;
  }
}
</examples>

<instructions>
Apply this exact error handling pattern to all endpoints. If an endpoint doesn't interact with the database, omit the NotFoundError check but keep the try-catch structure and logging.
</instructions>
```

### The Mechanism in LLM-to-LLM Communication

When one LLM writes a prompt for another to execute:

- The executing LLM encodes ALL patterns shown, regardless of labels
- "Don't do X" requires the LLM to first construct X, then negate it
- Direct positive examples create stronger, more reliable execution
- Pattern consistency across examples ensures predictable behavior

Key principle: In LLM-to-LLM communication, show exactly what you want done. Never show
what you don't want, even as a counterexample.

## Goals Over Process in LLM-Readable Prompts

When writing prompts for LLM execution (commands, workflows, agents), focus on clear
outcomes rather than micro-managing steps. LLMs can figure out implementation details.

Remember: The model executing your prompt is likely more advanced than the model that
created it. A prompt written by GPT-4 might be executed by Claude 3.5 Sonnet or GPT-4o.
Even prompts written by older versions of the same model will be executed by newer,
smarter versions. Trust the executing model's superior capabilities.

### The Over-Prescription Problem in LLM-to-LLM Communication

Overly prescriptive prompts create problems when LLMs execute them:

- Waste tokens on process details the executing LLM can determine
- Prevent the executing model from using its superior capabilities
- Create brittle workflows that break with slight context changes
- Force unnecessary decision trees that add complexity
- Reduce the LLM's ability to handle edge cases intelligently

### Writing Goal-Focused Prompts for LLMs

For command workflows and agent prompts:

Over-prescriptive command:

```xml
<task>
Step 1: Open each TypeScript file
Step 2: Search for interfaces
Step 3: For each interface:
  a. Check if it has a name starting with 'I'
  b. If yes:
     i. Create a new name without 'I'
     ii. Search for all usages
     iii. Replace each usage
  c. If no:
     i. Skip to next interface
Step 4: Save the file
Step 5: Run type checking
</task>
```

Goal-focused command:

```xml
<task>
Remove the 'I' prefix from all TypeScript interface names throughout the codebase, updating all references. Ensure type checking still passes.
</task>
```

For agent prompts:

Over-prescriptive agent prompt:

```xml
<instructions>
1. First, read the package.json file
2. Then, look at each dependency
3. For each dependency, check if there's a newer version
4. If there is, update it
5. Then run npm install
6. Then run the tests
7. If tests fail, revert the change
</instructions>
```

Goal-focused agent prompt:

```xml
<objective>
Update all dependencies to their latest compatible versions while ensuring all tests continue to pass.
</objective>

<constraints>
- Don't update major versions that would break compatibility
- Keep the application functional throughout the process
</constraints>
```

### Principles for LLM-Executable Prompts

Describe outcomes clearly: State what success looks like, not how to achieve it.

Set boundaries, not algorithms: Define constraints and requirements, let the LLM
determine the approach.

Use natural language: Write prompts as you would explain a task to a competent
colleague.

Trust the LLM's capabilities: Modern LLMs can handle file operations, code analysis, and
complex refactoring without step-by-step instructions.

### When Detailed Steps ARE Needed

Include specific steps only when:

- The order is critical and non-obvious
- Domain-specific requirements must be followed exactly
- You're establishing a specific pattern to be replicated
- The process itself is the goal (e.g., "Follow our team's PR review checklist")

Example where steps matter:

```xml
<task>
Implement our team's database migration protocol
</task>

<required-steps>
1. Create migration with timestamp prefix
2. Write both up() and down() methods
3. Test rollback before committing
4. Document breaking changes in MIGRATIONS.md
</required-steps>

<reason>
These steps are required by our deployment pipeline and cannot be skipped or reordered.
</reason>
```

Key principle: In LLM-to-LLM communication, clarity about the goal is more valuable than
detailed process instructions.

## Structural Delimiters for LLM Consumption

Modern LLMs are trained to recognize XML-style tags, making them highly effective for
LLM-to-LLM communication. Use them to create unambiguous boundaries that LLMs can
reliably parse.

Now that you understand pattern reinforcement and goal-focused prompting, you need to
know how to structure prompts so LLMs can parse them reliably.

When to use XML tags:

- Multiple distinct sections that need clear separation
- When you need to reference specific parts later in the prompt
- Complex prompts with different types of content (context, task, examples, constraints)
- Creating reusable command workflows or agent prompts

```xml
<context>
Current database has 50M records across 12 tables
</context>

<task>
Optimize the query to run in under 2 seconds
</task>

<constraints>
Cannot modify indexes or table structure
</constraints>
```

When to skip XML tags:

- Single, straightforward instructions
- Simple one-paragraph prompts
- When the prompt has natural flow without needing boundaries

```
# Good without XML - simple and clear:
Update all TypeScript interfaces to use the 'readonly' modifier for properties that shouldn't be mutated.

# Better with XML - multiple components:
<objective>
Refactor the authentication system
</objective>

<requirements>
- Maintain backward compatibility
- Use the new OAuth2 library
- Update all affected tests
</requirements>

<examples>
// Old pattern
const auth = new BasicAuth(username, password);

// New pattern
const auth = new OAuth2Client(clientId, clientSecret);
</examples>
```

Guidelines for XML structure:

- **Use semantic names, not numbers**: `<task-preparation>` not `<phase-1>`,
  `<create-pr>` not `<step-6>`
  - Numbered tags are brittle: reordering requires renumbering all tags and references
  - Semantic tags are self-documenting: `<validation-and-review>` tells you what it does
  - Example: A workflow with `<task-preparation>`, `<execution>`, `<review>` stays clear
    even when phases are added or reordered
- Be consistent with tag names throughout your codebase (always use `<task>` not
  sometimes `<objective>`)
- Use semantically meaningful tag names that describe the content
- Tags should enhance clarity, not add complexity for its own sake
- LLMs parse these more literally than humans would

## Writing Prompts for LLM Consumption

When creating prompts that other LLMs will read and execute (command files, agent
prompts, workflows), remember that LLMs parse more literally than humans. Ambiguity that
humans resolve through context will confuse LLMs.

### Key Differences from Human-Readable Prompts

LLMs need explicit context: Humans infer relationships and context. LLMs need everything
spelled out.

Good example:

```
"Update the webpack.config.js to enable source maps in development mode by setting devtool: 'source-map'"
```

Avoid vague references like "update the config like we discussed"

Consistent terminology matters: Use the same terms throughout. Don't vary vocabulary for
style.

Good example:

```
"Update the component... update the component... update the component..."
```

Avoid varying terms like "modify the component... update the element... change the
widget"

Unambiguous references: Be specific about what you're referring to.

Good example:

```
"After updating the UserProfile component, test the user authentication functionality"
```

Avoid ambiguous pronouns like "After updating it, test the functionality"

### Structure for LLM Parsing

Use clear section markers: Help the LLM understand the prompt structure.

```xml
<context>
Working in a Next.js 14 application with TypeScript and Tailwind CSS
</context>

<objective>
Create a reusable modal component
</objective>

<requirements>
- Use Radix UI primitives for accessibility
- Include enter/exit animations
- Support both controlled and uncontrolled modes
</requirements>

<output>
A single Modal.tsx file with the complete implementation
</output>
```

Order matters for context building: Put foundational information first.

Good order:

1. Environment/context
2. Overall objective
3. Specific requirements
4. Constraints
5. Examples
6. Output format

This allows the LLM to build understanding progressively.

### Common Patterns for Reliability

For refactoring tasks: Be explicit about preservation.

```xml
<task>
Refactor all class components to functional components with hooks
</task>

<preserve>
- All existing functionality
- Component prop interfaces
- Test coverage
</preserve>
```

For code generation: Specify integration points.

```xml
<task>
Add user authentication to the application
</task>

<integration>
- Use existing Router from src/router/index.ts
- Store auth state in existing Redux store
- Follow existing API client patterns in src/api/
</integration>
```

For analysis tasks: Define evaluation criteria.

```xml
<task>
Review the codebase for performance issues
</task>

<focus-areas>
- Unnecessary re-renders in React components
- N+1 query problems in API endpoints
- Large bundle sizes from imports
- Missing memoization opportunities
</focus-areas>
```

### Testing LLM-Readable Prompts

When you write a prompt file, consider:

- Can another LLM execute this without your implicit knowledge?
- Are all terms defined or demonstrated through examples?
- Is the success criteria clear and measurable?
- Would a different LLM interpret this the same way?

## Few-Shot Example Guidelines for LLM Authors

When an LLM creates prompts with examples for another LLM to execute, pattern
consistency is critical. Examples are the primary teaching mechanism. This builds on the
"Pattern Reinforcement Through Examples" section above.

How many examples for LLM execution?

- 0 examples: Only for standard operations the LLM knows well (e.g., "format as JSON")
- 1-2 examples: When you need specific format but pattern is simple
- 3-5 examples: Optimal for teaching new patterns - enough variety without overwhelming
- 5+ examples: When establishing complex patterns or handling many edge cases

Critical rule for LLM-generated examples: All examples must follow identical structure.

Good - Consistent pattern for LLM to follow:

````xml
<examples>
// Convert async/await to promises:
// Before:
async function getData() {
  const result = await fetch('/api/data');
  return await result.json();
}

// After:
function getData() {
  return fetch('/api/data')
    .then(result => result.json());
}

// Before:
async function saveUser(data) {
  const user = await createUser(data);
  await sendEmail(user.email);
  return user;
}

// After:
function saveUser(data) {
  return createUser(data)
    .then(user => {
      return sendEmail(user.email).then(() => user);
    });
}
</examples>

Inconsistent structure confuses LLMs. Avoid examples where:
- First example shows just the result summary
- Second example shows before/after code
- Third example shows only a text rule

This inconsistency prevents the LLM from learning a reliable pattern.

Ordering examples for LLM consumption:

1. Most common case first - This anchors the pattern
2. Variations next - Show how pattern adapts
3. Edge cases last - Only if necessary
4. Never include counter-examples - LLMs will reproduce them

Example placement in prompt files:

```xml
<task>
Convert all arrow functions to regular functions
</task>

<rules>
Preserve all functionality and bindings
</rules>

<examples>
// Pattern demonstrations go here
// Each showing the exact transformation
</examples>

<apply-to>
All files in src/components/
</apply-to>
````

## Token Efficiency for LLM Clarity

When writing prompts for LLM consumption, prioritize unambiguous interpretation over
brevity. Clear communication between LLMs is more important than token count. Remember
the principle from earlier: clarity over brevity.

Clarity-focused optimizations:

Remove redundancy, keep precision:

Good example:

```
"Update the webpack.config.js file"
```

Avoid unnecessarily verbose phrasing like "In order to accomplish the task of updating
the configuration file" or overly compressed ambiguous phrases like "update config"

Use consistent terminology:

Good example:

```
"Update the component, then update the module, then update the element"
```

Avoid varying terms for style like "Modify the component, then alter the module, then
change the element"

Combine related instructions when logical:

Good example:

```
"Validate input: ensure it's a non-null string"
```

Avoid over-separated instructions like "First, validate the input. Second, check it's
not null. Third, verify it's a string."

When compression hurts LLM comprehension:

Avoid these "optimizations" that confuse LLMs:

- Ambiguous pronouns ("it", "that", "this") without clear antecedents
- Omitting articles that clarify meaning ("the UserService" vs "UserService")
- Context-dependent abbreviations not defined in the prompt
- Removing qualifiers that specify scope ("all", "only", "except")

Example of good balance:

Good example:

```xml
<task>
Fix the authentication bug in the login component where users remain logged in after session expiry
</task>
```

Avoid over-compressed descriptions like "fix auth bug in login"

Key principle: In LLM-to-LLM prompts, every word that adds clarity is worth including.
Compression should never introduce ambiguity.

## Prompt Composability

When building command workflows and agent systems, design prompts that can be combined
and reused across different contexts.

### Modular Prompt Design

Create prompts that work as building blocks:

```xml
<!-- Base refactoring prompt -->
<prompt id="refactor-base">
  <context>
    Working in a TypeScript codebase with strict mode enabled
  </context>
  <preserve>
    All existing functionality and type contracts
  </preserve>
</prompt>

<!-- Specific refactoring that builds on base -->
<prompt extends="refactor-base">
  <task>
    Convert Promise chains to async/await syntax
  </task>
  <constraints>
    Maintain error handling semantics
  </constraints>
</prompt>
```

### Referencing Other Prompts

Design prompts that can reference and build upon each other:

```xml
<workflow>
  <step name="analyze">
    <description>
    Identify all components using deprecated lifecycle methods
    </description>
    <output>List of components needing updates</output>
  </step>

  <step name="refactor" depends-on="analyze">
    <description>
    Update each identified component to use modern hooks
    </description>
    <input>List from analyze step</input>
  </step>

  <step name="verify" depends-on="refactor">
    <description>
    Run tests and ensure no regressions
    </description>
  </step>
</workflow>
```

### Context Inheritance

Allow prompts to inherit and override context:

```xml
<!-- Parent context -->
<base-context>
  <environment>Next.js 14, TypeScript 5.2</environment>
  <style-guide>Airbnb JavaScript Style Guide</style-guide>
</base-context>

<!-- Child prompt inherits and extends -->
<command inherits="base-context">
  <task>Create new API endpoint</task>
  <additional-context>
    <database>PostgreSQL with Prisma ORM</database>
  </additional-context>
</command>
```

### Parameterized Prompts

Create reusable templates with parameters:

```xml
<template name="add-feature">
  <parameters>
    <param name="feature-name" required="true"/>
    <param name="integration-points" required="true"/>
    <param name="test-requirements" default="unit and integration tests"/>
  </parameters>

  <task>
    Add {{feature-name}} to the application
  </task>

  <requirements>
    - Integrate with {{integration-points}}
    - Include {{test-requirements}}
    - Follow existing patterns in the codebase
  </requirements>
</template>

<!-- Usage -->
<execute template="add-feature">
  <feature-name>user notifications</feature-name>
  <integration-points>Redux store and WebSocket service</integration-points>
</execute>
```

### Composition Patterns

Sequential composition: Tasks that must run in order

```xml
<sequence>
  <do>Analyze current implementation</do>
  <then>Identify optimization opportunities</then>
  <then>Apply optimizations</then>
  <finally>Measure performance improvements</finally>
</sequence>
```

Parallel composition: Tasks that can run simultaneously

```xml
<parallel>
  <task>Update component styles</task>
  <task>Update component tests</task>
  <task>Update component documentation</task>
</parallel>
```

Conditional composition: Tasks based on conditions

```xml
<conditional>
  <if condition="uses TypeScript">
    <task>Update type definitions</task>
  </if>
  <else>
    <task>Add JSDoc comments</task>
  </else>
</conditional>
```

### Best Practices for Composable Prompts

1. Clear interfaces: Define what each prompt expects and produces
2. Minimize dependencies: Keep prompts loosely coupled
3. Consistent naming: Use predictable names for reusable components
4. Documentation: Include descriptions of when and how to use each prompt
5. Version compatibility: Note which versions of tools/frameworks prompts work with

Key principle: Design prompts like you would design functions - focused, reusable, and
composable.

## Common Pitfalls in LLM-to-LLM Prompts

- Ambiguous instructions: "Update the code" vs "Update all React components to use hooks
  instead of class syntax"
- Inconsistent terminology: Switching between "component", "element", "widget" for the
  same thing
- Showing anti-patterns: Including "wrong" examples that the LLM will reproduce
- Over-prescriptive steps: Micro-managing instead of stating clear goals
- Unclear references: Using "it", "that", "this" without clear antecedents
- Missing context: Assuming the LLM knows project-specific conventions
- Inconsistent examples: Examples that don't follow the same pattern structure
