Semantic Kernel Integration
The StoryCAD API is designed for use with Microsoft Semantic Kernel. Every public method on StoryCADApi is decorated with [KernelFunction] and [Description] attributes, making the API directly registerable as an SK plugin.
How It Works
Semantic Kernel uses [KernelFunction] attributes to discover methods that an LLM can invoke via function calling. The [Description] attributes provide the LLM with documentation about what each method does, its parameters, and its constraints.
// From StoryCADAPI.cs: every method looks like this:
[KernelFunction]
[Description("Creates a new empty story outline from a template.")]
public async Task<OperationResult<List<Guid>>> CreateEmptyOutline(
string name, string author, string templateIndex)
# (no direct Python equivalent: [KernelFunction]/[Description] attributes and
# the OperationResult<T> return type are .NET / Semantic Kernel concepts.)
# In Python, the same method is just a plain call that returns the payload
# directly and raises on error:
from storycad import StoryCAD
sc = StoryCAD(headless=True)
overview = sc.create_empty_outline("My Story", "Jake", template_index="0")[0]
When you register the API as a plugin, the LLM sees a catalog of 48+ functions it can call to create, query, and modify story outlines.
Registering as a Plugin
using Microsoft.SemanticKernel;
using StoryCADLib.Services.IoC;
using StoryCADLib.Services.API;
using CommunityToolkit.Mvvm.DependencyInjection;
// 1. Initialize StoryCADLib
BootStrapper.Initialise(headless: true);
// 2. Get the API instance
var api = Ioc.Default.GetRequiredService<StoryCADApi>();
// 3. Build the Semantic Kernel
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
var kernel = builder.Build();
// 4. Register the API as a plugin
kernel.ImportPluginFromObject(api, "StoryCAD");
# 1 + 2. Initialize StoryCADLib and get the API instance (BootStrapper.Initialise
# + Ioc.Default.GetRequiredService collapse into one call).
from storycad import StoryCAD
sc = StoryCAD(headless=True)
# 3 + 4. Building a Semantic Kernel and registering the API as an SK plugin have
# no direct Python equivalent: Kernel.CreateBuilder, AddOpenAIChatCompletion,
# and ImportPluginFromObject are .NET / Semantic Kernel concepts.
After step 4, the kernel exposes all [KernelFunction] methods as callable functions. The LLM can invoke them through function calling.
Enabling Automatic Function Calling
For the LLM to call API methods autonomously, enable automatic function calling in the execution settings:
using Microsoft.SemanticKernel.Connectors.OpenAI;
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
# (no direct Python equivalent: OpenAIPromptExecutionSettings and
# FunctionChoiceBehavior.Auto() are .NET / Semantic Kernel concepts.)
With Auto behavior, the LLM decides when to call functions based on the conversation context. It sees the [Description] text for each method and chooses which to invoke.
Building an Agent
Combine the plugin registration with a system prompt to create an agent that builds outlines through natural language:
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using StoryCADLib.Services.IoC;
using StoryCADLib.Services.API;
using CommunityToolkit.Mvvm.DependencyInjection;
// Initialize
BootStrapper.Initialise(headless: true);
var api = Ioc.Default.GetRequiredService<StoryCADApi>();
// Build kernel with plugin
var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o",
Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
var kernel = builder.Build();
kernel.ImportPluginFromObject(api, "StoryCAD");
// Chat service and settings
var chat = kernel.GetRequiredService<IChatCompletionService>();
var settings = new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// System prompt
var history = new ChatHistory();
history.AddSystemMessage(
"You are a story development assistant. You help authors create " +
"structured story outlines using the StoryCAD API. " +
"When the user describes a story idea: " +
"1) Create a new outline with CreateEmptyOutline, " +
"2) Add characters, problems, scenes, and settings as needed, " +
"3) Use beat sheets to structure the plot, " +
"4) Link elements together, " +
"5) Save the outline when asked.");
// Conversation loop
while (true)
{
Console.Write("\nYou: ");
var input = Console.ReadLine();
if (string.IsNullOrEmpty(input)) break;
history.AddUserMessage(input);
var response = await chat.GetChatMessageContentAsync(
history, settings, kernel);
history.AddAssistantMessage(response.Content!);
Console.WriteLine($"\nAssistant: {response.Content}");
}
# Initialize StoryCADLib (BootStrapper.Initialise + Ioc.Default.GetRequiredService
# collapse into one call).
from storycad import StoryCAD
sc = StoryCAD(headless=True)
# (no direct Python equivalent: building a Kernel, registering the API as an SK
# plugin, IChatCompletionService, OpenAIPromptExecutionSettings, ChatHistory, and
# the automatic-function-calling chat loop are all .NET / Semantic Kernel
# concepts. The `sc` object exposes the same story-building methods the agent
# would call autonomously, e.g.:)
overview = sc.create_empty_outline("My Story", "Jake", template_index="2")[0]
hero = sc.add_element(sc.item_type.Character, overview, "Protagonist")
problem = sc.add_element(sc.item_type.Problem, overview, "Central Problem")
sc.apply_beat_sheet_to_problem(problem, "Save The Cat")
sc.write_outline("/tmp/agent_demo.stbx")
In this example, the LLM autonomously calls CreateEmptyOutline, AddElement, UpdateElementProperty, ApplyBeatSheetToProblem, and other API methods as it builds the outline through conversation.
Multi-Step Agent Patterns
Analysis Then Modification
An agent that reads an existing outline, analyzes it, and suggests or applies improvements:
// Open an existing outline
await api.OpenOutline("my-story.stbx");
// The LLM can now:
// 1. Call GetAllElements to see the full outline
// 2. Call SearchForReferences to find orphan elements
// 3. Call GetKeyQuestions to identify undeveloped areas
// 4. Call UpdateElementProperty to fill in gaps
// 5. Call ApplyBeatSheetToProblem to add structure
# Open an existing outline
sc.open_outline("my-story.stbx")
# The same methods are available on `sc`:
# 1. sc.get_all_elements() to see the full outline
# 2. sc.search_for_references(element) to find orphan elements
# 3. sc.get_key_questions(element_type) to identify undeveloped areas
# 4. sc.update_element_property(element, "Prop", "value") to fill in gaps
# 5. sc.apply_beat_sheet_to_problem(problem, "Save The Cat") to add structure
Resource-Driven Development
An agent that uses StoryCAD’s built-in writing tools to guide story development:
// The LLM can combine resource APIs with element creation:
// 1. GetMasterPlotNames → choose a plot pattern
// 2. GetMasterPlotScenes → get the scene breakdown
// 3. AddElement (Scene) for each plot beat
// 4. GetConflictCategories → build character conflicts
// 5. ApplyConflictToProtagonist → apply to problems
// 6. GetKeyQuestions → guide deeper element development
# The same resource methods can be combined with element creation on `sc`:
# 1. sc.get_master_plot_names() → choose a plot pattern
# 2. sc.get_master_plot_scenes(plot) → get the scene breakdown
# 3. sc.add_element(sc.item_type.Scene, overview, name) for each plot beat
# 4. sc.get_conflict_categories() → build character conflicts
# 5. sc.apply_conflict_to_protagonist(problem, "text") → apply to problems
# 6. sc.get_key_questions(element_type) → guide deeper element development
Tips for System Prompts
When building SK agents with the StoryCAD plugin:
- Guide the workflow order: Instruct the LLM to create the outline first, then add elements, then link them. Without guidance, it may try to link elements before they exist.
- Explain GUID references: The LLM needs to understand that
Protagonist,Setting, and similar fields take GUIDs of other elements, not text values. - Suggest resource usage: Prompt the LLM to use
GetExamples,GetConflictCategories, and other resource methods rather than inventing values. - Handle OperationResult: Instruct the LLM to check
IsSuccessbefore proceeding and to reportErrorMessagewhen operations fail.
NuGet Dependencies
To use Semantic Kernel with StoryCADLib, add these packages:
<PackageReference Include="StoryCADLib" Version="4.2.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.*" />
For OpenAI specifically:
<PackageReference Include="Microsoft.SemanticKernel.Connectors.OpenAI" Version="1.*" />
See the Story Diagnostic Agent and StoryCADCritter samples for complete working examples.