# How to Create and Register Custom Function Plugins in AntSK: A Complete Guide

> Learn how to create and register custom function plugins in AntSK by marking .NET methods with DescriptionAttribute. Automatically index your functions for Semantic Kernel integration.

- Repository: [AIDotNet/antsk](https://github.com/aidotnet/antsk)
- Tags: how-to-guide
- Published: 2026-02-24

---

**Developers create custom function plugins in AntSK by marking .NET methods with a `DescriptionAttribute` containing the token "AntSK", enabling the `FunctionService` to automatically index them for registration into the Semantic Kernel as native functions or prompt-based plugins.**

AntSK is an open-source .NET knowledge management system that leverages Microsoft Semantic Kernel to extend LLM capabilities with custom business logic. To integrate your own code into AntSK's AI workflows, you must understand how the framework discovers, indexes, and registers methods as callable functions that LLMs can invoke during prompt execution.

## How AntSK Discovers Custom Functions

AntSK uses reflection-based scanning to locate candidate methods across all loaded assemblies. This discovery process is centralized in [`FunctionService.cs`](https://github.com/aidotnet/antsk/blob/main/FunctionService.cs).

### The "AntSK" Token Requirement

In [`src/AntSK.Domain/Domain/Service/FunctionService.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK.Domain/Domain/Service/FunctionService.cs) (lines 36-88), the `SearchMarkedMethods` method scans every loaded assembly—including the main AntSK assembly and any additional dependencies—for methods decorated with `System.ComponentModel.DescriptionAttribute`. **Only methods whose description text contains the literal string "AntSK" are indexed** for plugin registration.

### Key Generation and Metadata Caching

For each discovered method, the service generates a sanitized unique key using the assembly, type, and method names:

```csharp
var key = $"{method.DeclaringType.Assembly.GetName().Name}_{method.DeclaringType.Name}_{method.Name}";
key = Regex.Replace(key, "[^a-zA-Z0-9_]", "_");

```

The method metadata is then stored in two internal concurrent dictionaries:
- `_methodCache` maps the key to the `MethodInfo` object
- `_methodInfos` stores tuples containing the description, return type description, and parameter information

This indexing occurs at startup when `FunctionService` is instantiated via dependency injection in [`src/AntSK/Program.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK/Program.cs) (lines 71-72).

## Registering Functions with the Semantic Kernel

Once indexed, functions must be imported into a `Kernel` instance before they can be invoked by LLM prompts.

### The Registration Entry Point

The `KernelService.ImportFunctionsByApp` method in [`src/AntSK.Domain/Domain/Service/KernelService.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK.Domain/Domain/Service/KernelService.cs) (lines 59-74) orchestrates the loading process. It first checks if a plugin named **"AntSKFunctions"** already exists in the kernel to prevent duplicate registration, then proceeds to import both API-based and native .NET functions.

### Native Function Import Process

Inside `ImportNativeFunction` (lines 77-96), the service reads the comma-separated `NativeFunctionList` property from the `Apps` entity. For each function key present in this list, the service retrieves the cached `MethodInfo` and creates a `KernelFunction` using `Kernel.CreateFunctionFromMethod`. These functions are then grouped under the **"AntSKFunctions"** plugin name via `_kernel.ImportPluginFromFunctions`.

## Creating a Native Function Plugin (Code-First Approach)

To expose your business logic as a callable function, follow these implementation steps.

### Step 1: Define the Method with Proper Attributes

Create a static method in any accessible class, ensuring the `DescriptionAttribute` contains "AntSK":

```csharp
using System.ComponentModel;

namespace MyAntSKPlugins;

public class UserInfoPlugin
{
    [Description("AntSK: GetCurrentUserInfo - Retrieves user details by ID")]
    public static string GetCurrentUserInfo(
        [Description("The unique identifier of the user")] string userId)
    {
        // Your business logic here
        return $"{{\"userId\":\"{userId}\",\"name\":\"John Doe\"}}";
    }
}

```

### Step 2: Register the Assembly for Discovery

Ensure `FunctionService` scans your assembly by adding it to the constructor call in [`Program.cs`](https://github.com/aidotnet/antsk/blob/main/Program.cs):

```csharp
builder.Services.AddSingleton(sp =>
    new FunctionService(sp, 
        [typeof(MyAntSKPlugins.UserInfoPlugin).Assembly]));

```

### Step 3: Configure the App to Use the Function

Add the generated function key to your app's `NativeFunctionList` property. The key follows the pattern `AssemblyName_TypeName_MethodName`:

```csharp
// Example configuration for an Apps entity
app.NativeFunctionList = "MyAntSKPlugins_UserInfoPlugin_GetCurrentUserInfo";

```

### Step 4: Invoke the Function

When `KernelService.GetKernelByApp(app)` builds the kernel, your function becomes callable:

```csharp
var kernel = kernelService.GetKernelByApp(app);
var result = await kernel.InvokeAsync(
    kernel.Plugins.GetFunction("AntSKFunctions", "GetCurrentUserInfo"),
    new() { ["userId"] = "12345" });

Console.WriteLine(result.GetValue<string>());

```

## Creating a Prompt-Based Plugin (No-Code Approach)

For scenarios requiring only prompt templates without compiled code, AntSK supports directory-based prompt plugins.

### Directory Structure and Files

Create a folder under the `plugins` directory (e.g., `plugins/HelloPlugin/`) containing:

**skprompt.txt:**

```text
Say hello to the user.
User: {{$input}}
Assistant: Hello, {{$input}}! Welcome to AntSK.

```

**config.json:**

```json
{
  "name": "HelloPlugin",
  "description": "Simple greeting plugin for AntSK",
  "execution_settings": {
    "default": {
      "temperature": 0.7
    }
  }
}

```

### Automatic Registration

The `KernelService.RegisterPluginsWithKernel` method (line 308) automatically imports all directories from `RepoFiles.SamplePluginsPath()` using `kernel.ImportPluginFromPromptDirectory`. Your prompt functions become available under the plugin name specified in [`config.json`](https://github.com/aidotnet/antsk/blob/main/config.json) (e.g., "HelloPlugin").

## Summary

- **Discovery requires the "AntSK" token**: Methods must use `[Description("AntSK: ...")]` to be indexed by `FunctionService`.
- **Keys are auto-generated**: The format is `AssemblyName_TypeName_MethodName`, sanitized to replace non-alphanumeric characters with underscores.
- **Registration is app-specific**: Functions are imported into the kernel only if their keys are listed in the `Apps.NativeFunctionList` property.
- **Two plugin types supported**: Native functions (.NET methods) and prompt-based plugins (directory with [`skprompt.txt`](https://github.com/aidotnet/antsk/blob/main/skprompt.txt) and [`config.json`](https://github.com/aidotnet/antsk/blob/main/config.json)).
- **Plugin name is "AntSKFunctions"**: All native custom functions are grouped under this plugin name in the Semantic Kernel.

## Frequently Asked Questions

### What token must be included in the DescriptionAttribute for AntSK to discover my method?

The description text must contain the literal string **"AntSK"** (case-sensitive). For example: `[Description("AntSK: Calculate sales tax")]`. Without this token, `FunctionService.SearchMarkedMethods` will ignore the method during assembly scanning.

### Can I register functions from external assemblies or NuGet packages?

Yes. Pass the external assembly to the `FunctionService` constructor in [`Program.cs`](https://github.com/aidotnet/antsk/blob/main/Program.cs) using `typeof(YourType).Assembly`. As long as the assembly is loaded and methods contain the "AntSK" description token, they will be indexed and available for registration in any app's `NativeFunctionList`.

### How do I prevent duplicate plugin registration when reusing Kernel instances?

The `ImportFunctionsByApp` method in `KernelService` explicitly checks `_kernel.Plugins.Any(p => p.Name == "AntSKFunctions")` and returns early if the plugin already exists. This guard clause prevents duplicate registration errors when the same kernel instance is reused across multiple operations.

### What is the difference between native functions and prompt-based plugins in AntSK?

**Native functions** are compiled .NET methods that execute business logic directly via `Kernel.CreateFunctionFromMethod`, offering full access to the application's services and data. **Prompt-based plugins** are declarative templates stored in [`skprompt.txt`](https://github.com/aidotnet/antsk/blob/main/skprompt.txt) files that rely entirely on the LLM for execution, requiring no compiled code but offering less programmatic control.