Skip to content

Writing Tool Instructions

This guide covers how to write effective instructions for Skills, SubAgents, and Collapse attributes. Good instructions are the difference between an agent that fumbles through tasks and one that executes them precisely.

The Instruction DSL

HPD-Agent uses a simple DSL (Domain Specific Language) for tool instructions:

FeatureSyntaxExample
Plain textString literals"Always verify before deleting"
Multi-lineNewlines or \n"Step 1: ...\nStep 2: ..."
Metadata interpolation{metadata.Property}"Search using {metadata.ProviderName}"
Dynamic expressionsMethod/property referencesnameof(GetInstructions) or direct call

Skill Instructions

Skills use the same dual-context architecture as collapsed tools:

ParameterLocationLifetimeUse For
functionResultConversation historyOne-time on activationAdditional context (appended to auto-generated message)
systemPromptSystem promptEvery turn while activeWorkflow rules, constraints

Important: The system automatically generates a base activation message:

"{SkillName} skill activated. Available functions: {FunctionList}"

Your functionResult is appended to this auto-generated message. Don't duplicate the activation info—use it only for additional context like tips, environment state, or configuration details. Pass null if the auto-generated message is sufficient.

Basic Format

csharp
SkillFactory.Create(
    name: "Research",
    description: "Deep research on a topic",           // BEFORE activation
    functionResult: null,  // Auto-generated: "Research skill activated. Available functions: WebSearch, DeepRead"
    systemPrompt: @"
        RESEARCH WORKFLOW:
        1. Start with a broad web search
        2. Identify key sources and themes
        3. Deep-dive into the most relevant sources
        4. Synthesize findings into a coherent summary
        5. Cite all sources used",                     // Persistent
    "SearchTools.WebSearch",
    "SearchTools.DeepRead"
);

Do's ✓

Use functionResult for additional context only:

csharp
// Good: Additional info beyond the auto-generated message
functionResult: "Connected to: production_db. Tip: Use Query('SHOW TABLES') first."

// Good: Auto-generated message is sufficient
functionResult: null

Put workflow rules in systemPrompt:

csharp
systemPrompt: @"
    WORKFLOW:
    1. Query the database schema first (use Query with 'DESCRIBE tables')
    2. Identify relevant tables for the user's question
    3. Write and execute the actual query
    4. Format results as a markdown table

    RULES:
    - Always LIMIT results to 100 rows
    - Use JOINs instead of multiple queries when possible"

Include decision points in systemPrompt:

csharp
systemPrompt: @"
    1. Search the web for initial context
    2. IF the topic is code-related:
       - Also search the codebase
       - Check documentation
    3. IF sources conflict:
       - Prefer official documentation
       - Note the discrepancy to the user"

Specify output format:

csharp
systemPrompt: @"
    After analysis, provide:
    - Summary (2-3 sentences)
    - Key findings (bullet points)
    - Confidence level (High/Medium/Low)
    - Sources used"

Don'ts ✗

Redundant with auto-generated message (wastes tokens):

csharp
// Bad: Duplicates auto-generated "{SkillName} skill activated. Available functions: ..."
functionResult: "Database skill activated. Query, Insert, Update, Delete available."

Too vague:

csharp
// Bad: No actionable guidance
functionResult: null
systemPrompt: null  // No workflow!

Too long:

csharp
// Bad: Wall of text the agent will ignore
systemPrompt: @"
    [500 words of detailed instructions covering every edge case...]"

Contradictory:

csharp
// Bad: Conflicting guidance
systemPrompt: @"
    Always use the fastest approach.
    Always be thorough and check everything."

Toolkit Instructions: FunctionResult vs SystemPrompt

The [Toolkit] attribute has two instruction slots with different purposes:

csharp
[Toolkit(
    "Database operations",
    FunctionResult: "...",   // One-time, appended to auto-generated message
    SystemPrompt: "..."      // Persistent, in system instructions
)]

Important: Collapsed toolkits automatically generate a base expansion message:

"{ToolkitName} expanded. Available functions: {FunctionList}"

Your FunctionResult is appended to this auto-generated message. Don't duplicate the expansion info—use it only for additional context.

When to Use FunctionResult

Additional context beyond auto-generated message:

csharp
// Good: Runtime info the auto-message doesn't provide
FunctionResult: @"
    Connected to: production database
    Tables: users, orders, products, inventory
    Tip: Use Query('SHOW TABLES') to see all tables"

// Good: Auto-generated message is sufficient
FunctionResult: null

When to Use SystemPrompt

Critical rules that must be enforced every turn:

csharp
SystemPrompt: @"
    DATABASE SAFETY RULES (MUST FOLLOW):
    - NEVER execute DELETE without a WHERE clause
    - NEVER drop tables or modify schema
    - Always use parameterized queries for user input
    - LIMIT all SELECT queries to 1000 rows"

Behavioral constraints:

csharp
SystemPrompt: @"
    When using file operations:
    - Always confirm before overwriting existing files
    - Never access files outside the project directory
    - Log all write operations"

Do's ✓

Keep SystemPrompt short and critical:

csharp
// Good: Essential rules only
SystemPrompt: "Never DELETE without WHERE. Always use transactions for multi-step operations."

Use FunctionResult for additional context:

csharp
// Good: Additional info beyond auto-generated message
FunctionResult: @"
    Working directory: /home/user/project
    Tip: Use ListDir before ReadFile to verify paths"

Don'ts ✗

Don't duplicate the auto-generated message (wastes tokens):

csharp
// Bad: The system already generates this
FunctionResult: "FileTools expanded. Available functions: ReadFile, WriteFile, DeleteFile"

Don't put ephemeral info in SystemPrompt:

csharp
// Bad: Status info doesn't belong in persistent instructions
SystemPrompt: "Connected to server at 192.168.1.1. Session started at 10:42 AM."

Don't duplicate between both:

csharp
// Bad: Same content in both places wastes tokens
FunctionResult: "Always use transactions",
SystemPrompt: "Always use transactions"

Don't put critical rules only in FunctionResult:

csharp
// Bad: Agent might forget this rule in later turns
FunctionResult: "CRITICAL: Never delete without confirmation"
// Should be in SystemPrompt instead

Dynamic Instructions with Expressions

For runtime-generated instructions, use method or property references:

Static Method

csharp
[Toolkit(
    "Search operations",
    FunctionResult: nameof(GetSearchStatus)
)]
public class SearchToolkit
{
    public static string GetSearchStatus()
    {
        var providers = EnabledProviders.Select(p => p.Name);
        return $"Available providers: {string.Join(", ", providers)}";
    }
}

Instance Method

csharp
[Toolkit(
    "Database operations",
    FunctionResult: nameof(GetConnectionInfo)
)]
public class DatabaseToolkit
{
    private readonly string _connectionString;

    public string GetConnectionInfo()
    {
        return $"Connected to: {_connectionString}";
    }
}

Static Property

csharp
[Toolkit(
    "File operations",
    SystemPrompt: nameof(FileRules)
)]
public class FileToolkit
{
    public static string FileRules => @"
        FILE SAFETY:
        - Confirm before overwrite
        - No access outside project root";
}

Do's ✓

Use expressions for dynamic content:

csharp
// Good: Runtime state in instructions
FunctionResult: nameof(GetAvailableModels)

public static string GetAvailableModels()
{
    return $"Models loaded: {string.Join(", ", _loadedModels)}";
}

Don'ts ✗

Don't use string literals where expressions are expected:

csharp
// Bad: Triggers HPDAG0103 warning
[Toolkit("...", FunctionResult: "literal string")]
// The generator expects a method reference, not a string

Don't use complex expressions:

csharp
// Bad: Operators not supported
FunctionResult: GetA() + GetB()

// Good: Wrap in a method
FunctionResult: nameof(GetCombinedInfo)
public static string GetCombinedInfo() => GetA() + GetB();

SubAgent Instructions

SubAgents receive instructions via AgentConfig.SystemInstructions. These define the SubAgent's persona and behavior.

Basic Format

csharp
SubAgentFactory.Create(
    name: "Code Reviewer",
    description: "Reviews code for quality and best practices",
    agentConfig: new AgentConfig
    {
        SystemInstructions = @"
            You are an expert code reviewer specializing in C# and .NET.

            When reviewing code:
            1. Check for bugs and logic errors
            2. Evaluate code style and readability
            3. Identify security vulnerabilities
            4. Suggest performance improvements
            5. Rate overall quality (1-10)

            Be constructive and specific. Provide code examples for suggestions."
    }
);

Do's ✓

Define a clear persona:

csharp
SystemInstructions: @"
    You are a security auditor with expertise in OWASP Top 10 vulnerabilities.
    Your role is to identify security issues, not general code quality."

Set boundaries:

csharp
SystemInstructions: @"
    You are a documentation writer.

    SCOPE:
    - Write and update documentation
    - Generate API docs from code
    - Create examples and tutorials

    OUT OF SCOPE:
    - Do not modify source code
    - Do not make architectural decisions"

Specify output format:

csharp
SystemInstructions: @"
    After each review, provide:

    ## Summary
    [2-3 sentence overview]

    ## Issues Found
    - [Issue 1]: [Severity] - [Description]

    ## Recommendations
    1. [Specific actionable recommendation]

    ## Rating: X/10"

Don'ts ✗

Don't be vague about the role:

csharp
// Bad: What does "help" mean?
SystemInstructions: "Help the user with their code."

Don't give conflicting goals:

csharp
// Bad: Speed vs thoroughness conflict
SystemInstructions: @"
    Be extremely thorough and check everything.
    Also, be as fast as possible and don't waste time."

Metadata Interpolation

Use {metadata.PropertyName} in descriptions for runtime values:

csharp
public class SearchMetadata : IToolMetadata
{
    public string ProviderName { get; set; } = "Default";
    public int MaxResults { get; set; } = 10;
}

[AIFunction<SearchMetadata>]
[AIDescription("Search using {metadata.ProviderName}, returning up to {metadata.MaxResults} results")]
public async Task<string> Search(string query) { }

Where It Works

  • [AIDescription] on functions
  • [AIDescription] on parameters
  • description in [Toolkit("...")]
  • description in SkillFactory.Create()
  • description in SubAgentFactory.Create()

Do's ✓

Use for user-facing configuration:

csharp
[AIDescription("Query the {metadata.DatabaseName} database")]

Provide defaults in metadata class:

csharp
public string ProviderName { get; set; } = "Default";  // Never null

Don'ts ✗

Don't interpolate in instructions (only descriptions):

csharp
// Bad: Won't work in skill systemPrompt
systemPrompt: "Search using {metadata.ProviderName}"

// Good: Use dynamic expression instead
systemPrompt: nameof(GetInstructions)
public string GetInstructions() => $"Search using {_metadata.ProviderName}";

Common Patterns

The Checklist Pattern

For methodical tasks:

csharp
systemPrompt: @"
    PRE-FLIGHT CHECKLIST:
    □ Verify input parameters are valid
    □ Check user has required permissions
    □ Confirm target doesn't already exist

    EXECUTION:
    □ Perform the operation
    □ Verify success
    □ Log the action

    POST-FLIGHT:
    □ Return confirmation to user
    □ Clean up temporary resources"

The Decision Tree Pattern

For conditional workflows:

csharp
systemPrompt: @"
    1. Analyze the request type:

       IF bug report:
         → Search for similar issues
         → Check recent commits
         → Identify potential causes

       IF feature request:
         → Search existing features
         → Check roadmap/backlog
         → Assess feasibility

       IF question:
         → Search documentation
         → Search codebase
         → Provide answer with sources"

The Guardrails Pattern

For safety-critical operations:

csharp
SystemPrompt: @"
    GUARDRAILS - NEVER VIOLATE:
    ✗ Never execute commands with 'rm -rf'
    ✗ Never access files outside /project
    ✗ Never expose credentials or secrets
    ✗ Never bypass permission checks

    ALWAYS:
    ✓ Confirm destructive operations
    ✓ Validate user input
    ✓ Log sensitive operations"

The Expert Persona Pattern

For SubAgents:

csharp
SystemInstructions: @"
    You are Dr. Security, a cybersecurity expert with 20 years of experience.

    EXPERTISE:
    - OWASP Top 10 vulnerabilities
    - Penetration testing methodology
    - Secure coding practices
    - Compliance (SOC2, GDPR, HIPAA)

    COMMUNICATION STYLE:
    - Direct and technical
    - Always cite specific vulnerabilities (e.g., CWE-89)
    - Provide severity ratings (Critical/High/Medium/Low)
    - Include remediation steps

    LIMITATIONS:
    - You audit code, you don't write it
    - You identify issues, you don't fix them
    - Escalate findings over 'High' severity"

Instruction Length Guidelines

ContextRecommended LengthWhy
FunctionResult1-3 linesOrientation only, in history
SystemPrompt3-10 linesEvery turn, keep minimal but actionable
Skill functionResult1-2 linesTool availability notice
Skill systemPrompt5-15 linesWorkflow guidance
SubAgent SystemInstructions10-30 linesPersona + scope + style

Rule of thumb: If you can't read it in 10 seconds, it's too long.


Debugging Instructions

Check Generated Code

The source generator creates readable code. Find it in:

obj/Debug/net8.0/generated/HPD.Agent.SourceGenerator/

Look for:

  • {ToolkitName}Registry.g.cs - Toolkit registration
  • Container function implementations
  • Resolver methods for dynamic descriptions

Common Issues

SymptomLikely CauseFix
Instructions not appearingWrong slot (FunctionResult vs SystemPrompt)Check which slot matches your need
Instructions appearing every turnUsed SystemPrompt for ephemeral infoMove to FunctionResult
Dynamic instructions staticUsing literal instead of expressionUse nameof(Method)
Compile error HPDAG0103String literal in expression slotUse method reference

Summary

Instruction TypeWhereWhen VisibleUse For
Skill functionResultAppended to auto-generated activation messageOnce on activationAdditional context (tips, state)
Skill systemPromptSystem promptEvery turn while activeWorkflow steps
Toolkit FunctionResultAppended to auto-generated expansion messageOnce on expansionAdditional context (tips, state)
Toolkit SystemPromptSystem promptEvery turn while expandedCritical rules
SubAgent SystemInstructionsSystem promptAlwaysPersona, scope
{metadata.X}DescriptionsTool selectionDynamic labels

Golden rules:

  1. Be specific, not vague
  2. Be concise, not comprehensive
  3. Put critical rules in SystemPrompt (persistent)
  4. Put additional context in FunctionResult (ephemeral, appended to auto-generated message)
  5. Don't duplicate auto-generated messages—they waste tokens
  6. Test your instructions—if the agent ignores them, they're too long or unclear

Released under the MIT License.