Octopus
Native Functions
SK native functions are C# methods decorated with [KernelFunction] and [Description] attributes. They are registered into the SK kernel and can be called by the planner or invoked directly alongside semantic functions.
Defining a Native Function Class
using Microsoft.SemanticKernel;
using System.ComponentModel;
// SK plugin class containing native functions
public class HRNativePlugin
{
private readonly ILeaveService _leaveService;
public HRNativePlugin(ILeaveService leaveService)
{
_leaveService = leaveService;
}
[KernelFunction("get_leave_balance")]
[Description("Returns the remaining leave balance for an employee. " +
"Use when the user asks how many leave days they have left.")]
public async Task<string> GetLeaveBalanceAsync(
[Description("The employee's ID number (e.g. EMP-1234)")]
string employeeId,
CancellationToken cancellationToken = default)
{
var balance = await _leaveService.GetBalanceAsync(employeeId, cancellationToken);
return $"Annual: {balance.AnnualRemaining} days, Sick: {balance.SickRemaining} days";
}
[KernelFunction("list_team_members")]
[Description("Lists the direct reports of a manager. Returns a JSON array of employee IDs.")]
public async Task<string> ListTeamMembersAsync(
[Description("The manager's employee ID")]
string managerId,
CancellationToken cancellationToken = default)
{
var team = await _leaveService.GetTeamAsync(managerId, cancellationToken);
return JsonSerializer.Serialize(team.Select(e => e.EmployeeId));
}
}
Registering Native Functions in OnStartAsync
public async Task OnStartAsync(IServiceProvider sp, CancellationToken ct)
{
var kernel = sp.GetRequiredService<ISKKernelRegistry>().Get("default");
// Scoped services need a scope — capture the factory, not the service
// The kernel will call these functions at planning time, not at startup
var scopeFactory = sp.GetRequiredService<IServiceScopeFactory>();
// Build the native plugin using the DI container
// Create a scope for the constructor injection
using var initScope = scopeFactory.CreateScope();
var leaveService = initScope.ServiceProvider.GetRequiredService<ILeaveService>();
var hrPlugin = new HRNativePlugin(leaveService);
// Register the plugin into the SK kernel
kernel.ImportPluginFromObject(hrPlugin, "HRPlugin");
await Task.CompletedTask;
}
Scoped services in native functions. If your native function needs scoped services (e.g.,
ILeaveService), you must create a scope per call — not at startup. Consider injecting IServiceScopeFactory into the native plugin class and creating a fresh scope in each method.
Native Functions vs MCP Tools
| Feature | SK Native Function | Octopus MCP Tool |
|---|---|---|
| Registration | Into SK kernel via ImportPluginFromObject | Into MCPToolRegistry |
| Callable by SK Planner | Yes — first-class SK citizen | Requires bridging (see Planner page) |
| Callable by Octopus agent | Via SK bridge only | Directly — native to Octopus |
| Parameter definition | [Description] attributes on method params | JSON Schema in ToolDefinition.InputSchema |
| Metadata for LLM | Attribute descriptions | Rich JSON Schema with types and examples |
Calling Native Functions Directly
// Call a native function directly (without the planner)
var result = await kernel.InvokeAsync(
pluginName: "HRPlugin",
functionName: "get_leave_balance",
arguments: new KernelArguments { ["employeeId"] = "EMP-1234" });
string balance = result.GetValue<string>()!;
// → "Annual: 15 days, Sick: 8 days"