Skip to content

Tool Context Engineering

As applications grow, the number of available tools can overwhelm the agent's context window. Tool Context Engineering is about managing this complexity through collapsing (hierarchical organization) and instruction placement (where guidance appears).

The Problem: Tool Bloat

An agent with 50+ tools faces issues:

  • Token waste: Every tool description consumes context
  • Decision fatigue: Too many choices slow down the agent
  • Irrelevant options: Most tools aren't needed for any given task
Agent with 50 tools:
┌─────────────────────────────────────────────────────────┐
│ WebSearch, CodeSearch, DocSearch, FileRead, FileWrite, │
│ FileDelete, GitCommit, GitPush, GitPull, GitStatus,    │
│ DatabaseQuery, DatabaseInsert, DatabaseUpdate, ...     │
│ (thousands of tokens just for tool definitions)        │
└─────────────────────────────────────────────────────────┘

The Solution: Hierarchical Collapsing

Collapse tools into containers that expand on demand:

Before expansion:              After user asks about files:
┌──────────────────┐           ┌──────────────────┐
│ SearchTools     │           │ SearchTools     │
│ FileTools       │    ──►    │ FileRead         │
│ GitTools        │           │ FileWrite        │
│ DatabaseTools   │           │ FileDelete       │
└──────────────────┘           │ GitTools        │
                               │ DatabaseTools   │
     4 tools visible           └──────────────────┘
                                    6 tools visible

This reduces initial token usage while keeping all capabilities accessible.


The Toolkit Attribute

Use [Toolkit("description")] to make a toolkit collapsible:

csharp
[Toolkit("File operations for reading, writing, and managing files")]
public class FileSystemToolkit
{
    [AIFunction]
    public string ReadFile(string path) { /* ... */ }

    [AIFunction]
    public void WriteFile(string path, string content) { /* ... */ }

    [AIFunction]
    public void DeleteFile(string path) { /* ... */ }
}

How the description works:

  • You provide the purpose (e.g., "File operations for reading, writing, and managing files")
  • The system auto-generates the function list (e.g., "Container FileSystemToolkit provides access to: ReadFile, WriteFile, DeleteFile")
  • The final description combines both: "{auto-generated function list}. {your description}"

Note: Providing a description enables collapsing. Without a description ([Toolkit] or [Toolkit(Name = "...")]), functions are always visible.

Toolkit with Dual-Context Instructions

The [Toolkit] attribute supports two instruction injection points:

csharp
[Toolkit(
    "Database operations",
    FunctionResult: "Use transactions for multiple operations.",
    SystemPrompt: "CRITICAL: Never execute DELETE without a WHERE clause. Always validate inputs."
)]
public class DatabaseToolkit
{
    // Functions...
}

FunctionResult vs SystemPrompt

These two properties serve different purposes and behave differently:

PropertyInjection TargetWhenPersistenceUse For
FunctionResultTool call resultOnce on expansionIn message historyListing functions, one-time guidance
SystemPromptSystem instructionsEvery turnConfigurableCritical rules, behavioral constraints

FunctionResult

Returned as the result of "calling" the container. It's a one-time message that stays in conversation history. This is appended to the auto-generated expansion message.

csharp
[Toolkit(
    "Search operations",
    FunctionResult: "Tip: Start with WebSearch for general queries."
)]
public class SearchToolkit { /* ... */ }

Agent sees on expansion:

"SearchToolkit expanded. Available functions: WebSearch, CodeSearch, DocSearch

Tip: Start with WebSearch for general queries."

SystemPrompt

Injected into the system instructions while the container is expanded. Use for rules that must be enforced every turn.

csharp
[Toolkit(
    "File operations",
    SystemPrompt: @"
        FILE OPERATION RULES:
        - Always check if a file exists before reading
        - Never overwrite without user confirmation
        - Use absolute paths only"
)]
public class FileSystemToolkit { /* ... */ }

During each turn while expanded:

System: You are a helpful assistant.

        FILE OPERATION RULES:
        - Always check if a file exists before reading
        - Never overwrite without user confirmation
        - Use absolute paths only

User: Read the config file.
Agent: (follows the rules because they're in system prompt)

Context Lifecycle

Understanding when instructions appear and disappear:

Turn 1: User asks about files
├── Agent calls FileToolkit container
├── FunctionResult returned: "FileToolkit expanded. Available functions: ReadFile, WriteFile..."
├── SystemPrompt injected: "FILE RULES: ..."
└── Turn ends

Turn 2: Agent reads a file
├── SystemPrompt still present (functions still expanded)
├── Agent uses ReadFile
└── Turn ends

Turn 3: User asks about something else
├── SystemPrompt cleared (by default)
├── File functions still available but rules gone
└── Turn ends

[If PersistSystemPromptInjections = true]
Turn 3: User asks about something else
├── SystemPrompt STILL present
├── Accumulating prompts can bloat context
└── Turn ends

CollapsingConfig

Configure collapsing behavior in AgentConfig:

csharp
var config = new AgentConfig
{
    Collapsing = new CollapsingConfig
    {
        // Master switch for C# tool collapsing
        Enabled = true,

        // Also collapse client-provided tools
        CollapseClientTools = false,

        // Max functions listed in container description
        MaxFunctionNamesInDescription = 10,

        // Keep SystemPrompt injections across turns (default: false)
        PersistSystemPromptInjections = false,

        // How skill instructions are delivered
        SkillInstructionMode = SkillInstructionMode.Both,

        // Runtime override: these toolkits never collapse even if they have descriptions
        NeverCollapse = new HashSet<string> { "CoreToolkit", "DebugToolkit" },

        // Custom instructions for MCP servers
        MCPServerInstructions = new Dictionary<string, string>
        {
            ["filesystem"] = "Always use absolute paths.",
            ["github"] = "Prefer GraphQL API over REST when possible."
        },

        // Instructions for client-provided tools
        ClientToolsInstructions = "These tools interact with the user's IDE."
    }
};

Configuration Options Explained

Enabled

csharp
Enabled = true  // Toolkits with descriptions are collapsible
Enabled = false // All functions exposed directly (no containers)

CollapseClientTools

csharp
CollapseClientTools = true  // Group client tools under a container
CollapseClientTools = false // Client tools always visible

MaxFunctionNamesInDescription

Controls how many function names appear in the container description:

csharp
MaxFunctionNamesInDescription = 10
// Container shows: "File operations (ReadFile, WriteFile, DeleteFile, ...)"

MaxFunctionNamesInDescription = 3
// Container shows: "File operations (ReadFile, WriteFile, DeleteFile)"

MaxFunctionNamesInDescription = 0
// Container shows: "File operations"

PersistSystemPromptInjections

csharp
PersistSystemPromptInjections = false // Recommended: Clear after each turn
PersistSystemPromptInjections = true  // Keep accumulating (use carefully)

Why false is recommended: If multiple containers expand, their SystemPrompts accumulate. Clearing prevents context bloat.

NeverCollapse

Runtime override to prevent specific toolkits from collapsing, even if they have descriptions:

csharp
NeverCollapse = new HashSet<string> { "CoreToolkit", "DebugToolkit" }

Use cases:

  • Core tools that should always be visible (e.g., file operations)
  • Debug/development toolkits that you don't want collapsed during testing
  • Environment-specific overrides (collapse in production, expand in development)

How it works:

  • The container is still generated at compile time (description still provided)
  • At runtime, ToolVisibilityManager treats toolkits in NeverCollapse as non-containers
  • Functions are shown directly; the container is hidden
csharp
// Toolkit has a description, so container is generated
[Toolkit("File operations for reading, writing, and managing files")]
public class FileToolkit { /* ... */ }

// But at runtime, if "FileToolkit" is in NeverCollapse:
// - Container is hidden
// - ReadFile, WriteFile, DeleteFile are visible directly

SkillInstructionMode

Controls how skill instructions are delivered:

csharp
public enum SkillInstructionMode
{
    PromptMiddlewareOnly,  // Instructions only in system prompt
    Both                    // Instructions in system prompt AND function result
}

PromptMiddlewareOnly

Most token-efficient. Instructions appear once in system prompt:

System: ... [Skill: Research] 1. Search web first 2. Search code if relevant ...

Both (Default)

Backward-compatible. Instructions appear in both places:

System: ... [Skill: Research] 1. Search web first ...
FunctionResult: "Skill activated. Instructions: 1. Search web first ..."

Dynamic Instructions with Expressions

Reference methods or properties for runtime-generated instructions:

csharp
[Toolkit(
    "Search operations",
    FunctionResult: nameof(GetAvailableFunctions),
    SystemPrompt: nameof(SearchRules)
)]
public class SearchToolkit
{
    private static readonly List<string> _enabledProviders = new() { "Web", "Code" };

    // Called at expansion time
    public static string GetAvailableFunctions()
    {
        return $"Available search providers: {string.Join(", ", _enabledProviders)}";
    }

    // Property for static rules
    public static string SearchRules => @"
        SEARCH RULES:
        - Always cite sources
        - Prefer recent results";

    [AIFunction]
    public async Task<string> WebSearch(string query) { /* ... */ }
}

MCP Server Instructions

Provide instructions for Model Context Protocol servers:

csharp
Collapsing = new CollapsingConfig
{
    MCPServerInstructions = new Dictionary<string, string>
    {
        ["filesystem"] = @"
            When using filesystem tools:
            - Always use absolute paths
            - Check file existence before reading
            - Request confirmation before deleting",

        ["github"] = @"
            When using GitHub tools:
            - Prefer the GraphQL API for bulk operations
            - Always include PR descriptions
            - Link related issues"
    }
}

These instructions are injected when the respective MCP server's tools are used.


Best Practices

1. Use FunctionResult for Orientation

csharp
FunctionResult: @"
    Available functions:
    - Query: Read data (SELECT)
    - Insert: Add records
    - Update: Modify records
    - Delete: Remove records (use carefully)

    For complex operations, use Query first to understand the schema."

2. Use SystemPrompt for Critical Rules

csharp
SystemPrompt: @"
    MANDATORY DATABASE RULES:
    1. NEVER execute DELETE without WHERE clause
    2. ALWAYS use parameterized queries
    3. LIMIT results to 1000 rows maximum"

3. Keep Instructions Concise

csharp
// Good: Brief, actionable
SystemPrompt: "Always use transactions for multiple operations. Rollback on any error."

// Bad: Novel-length instructions
SystemPrompt: "When working with databases, it's important to remember that... [500 words]"

4. Don't Persist Unless Necessary

csharp
// Recommended for most cases
PersistSystemPromptInjections = false

// Only use true when rules MUST persist across many turns
PersistSystemPromptInjections = true
csharp
// Good: Coherent grouping
[Toolkit("Git version control operations")]
public class GitToolkit { /* commit, push, pull, status, diff */ }

// Bad: Unrelated functions forced together
[Toolkit("Misc utilities")]
public class MiscToolkit { /* sendEmail, readFile, getCurrentTime, translateText */ }

Complete Example

csharp
// Agent configuration
var config = new AgentConfig
{
    Collapsing = new CollapsingConfig
    {
        Enabled = true,
        MaxFunctionNamesInDescription = 5,
        PersistSystemPromptInjections = false,
        SkillInstructionMode = SkillInstructionMode.PromptMiddlewareOnly,
        MCPServerInstructions = new Dictionary<string, string>
        {
            ["filesystem"] = "Use absolute paths. Confirm before delete."
        }
    }
};

// Toolkit with collapsing
[Toolkit(
    "Database operations for querying and modifying data",
    FunctionResult: nameof(GetDatabaseInfo),
    SystemPrompt: "CRITICAL: Use transactions for multi-step operations. Never DELETE without WHERE."
)]
public class DatabaseToolkit
{
    public static string GetDatabaseInfo() =>
        $"Connected to: {_connectionString}. Tables: {string.Join(", ", _tables)}";

    [AIFunction]
    [AIDescription("Execute a SELECT query")]
    public async Task<string> Query(string sql) { /* ... */ }

    [AIFunction]
    [AIDescription("Insert a new record")]
    public async Task<string> Insert(string table, string json) { /* ... */ }

    [AIFunction]
    [AIDescription("Update existing records")]
    [RequiresPermission]
    public async Task<string> Update(string table, string where, string json) { /* ... */ }

    [AIFunction]
    [AIDescription("Delete records (requires WHERE clause)")]
    [RequiresPermission]
    public async Task<string> Delete(string table, string where) { /* ... */ }
}

// Build agent
var agent = new AgentBuilder()
    .WithConfig(config)
    .WithTools<DatabaseToolkit>()
    .Build();

Agent's initial view:

Tools: DatabaseToolkit (Query, Insert, Update, Delete, ...)

After expansion:

System: ... CRITICAL: Use transactions for multi-step operations. Never DELETE without WHERE.

Tools: Query, Insert, Update, Delete

Recent message: "DatabaseToolkit expanded. Available functions: Query, Insert, Update, Delete

Connected to: prod-db. Tables: users, orders, products"

Summary

ConceptPurpose
[Toolkit("description")]Group toolkit functions into expandable container
FunctionResultOne-time orientation message on expansion (appended to auto-generated message)
SystemPromptPersistent rules while functions are active
CollapsingConfigGlobal collapsing behavior settings
CollapsingConfig.NeverCollapseRuntime override to prevent specific toolkits from collapsing
PersistSystemPromptInjectionsKeep/clear SystemPrompt between turns
SkillInstructionModeHow skill instructions are delivered
MCPServerInstructionsCustom guidance for MCP server tools

Effective context engineering keeps the agent focused, reduces token usage, and ensures critical rules are followed—without overwhelming the context window.

Released under the MIT License.