using System;using System.IO;using System.Text;using System.Text.Json;using Agent.Core.ToolCalling;namespace Lilith.Agent.SelfImprovement;internal static class ToolScaffoldGenerator{ public static string Generate( string sandboxRoot, string toolName, string description, string category, string? invokeBody) { if (string.IsNullOrWhiteSpace(toolName)) throw new ArgumentException("tool_name is required."); if (string.IsNullOrWhiteSpace(description)) throw new ArgumentException("description is required."); ToolCategory cat = ParseCategory(category); string safeName = SanitizeToolName(toolName); invokeBody ??= "return \"OK\";"; return cat switch { ToolCategory.Core => WriteCoreTool(sandboxRoot, safeName, description, invokeBody), ToolCategory.Addon => WriteAddonTool(sandboxRoot, safeName, description, invokeBody), ToolCategory.Self => WriteSelfTool(sandboxRoot, safeName, description, invokeBody), _ => throw new ArgumentOutOfRangeException(nameof(category)), }; } private static ToolCategory ParseCategory(string category) => category.Trim().ToLowerInvariant() switch { "core" => ToolCategory.Core, "addon" => ToolCategory.Addon, "self" => ToolCategory.Self, _ => throw new ArgumentException("category must be core, addon, or self."), }; private static string SanitizeToolName(string name) => name.Trim().ToLowerInvariant().Replace(' ', '_'); private static string WriteCoreTool(string root, string toolName, string description, string invokeBody) { string className = ToPascalCase(toolName) + "Tool"; string dir = Path.Combine(root, "Agent-Core", "Ollama", "ToolCalling", "Generated"); Directory.CreateDirectory(dir); string path = Path.Combine(dir, $"{className}.cs"); string lambdaBody = FormatLambdaBody(invokeBody); string body = $$""" using Agent.Core.ToolCalling; namespace Agent.Core.ToolCalling.Generated; /// <summary>Generated core tool — {{description}}</summary> public static class {{className}} { public const string Name = "{{toolName}}"; public static void Register(ToolRegistry registry) { registry.Register(new AgentTool( Name, "{{Escape(description)}}", _ => {{lambdaBody}}, category: ToolCategory.Core)); } } """; File.WriteAllText(path, body, Encoding.UTF8); PatchBuiltInTools(root, className); return path; } private static string WriteAddonTool(string root, string toolName, string description, string invokeBody) { string className = ToPascalCase(toolName) + "Tool"; string dir = Path.Combine(root, "Agent-Addons", "Tools", "Generated"); Directory.CreateDirectory(dir); string path = Path.Combine(dir, $"{className}.cs"); string lambdaBody = FormatLambdaBody(invokeBody); string body = $$""" using Agent.Core.ToolCalling; namespace Agent.Addons.Tools.Generated; /// <summary>Generated addon tool — {{description}}</summary> public static class {{className}} { public const string Name = "{{toolName}}"; public static void Register(ToolRegistry registry) { registry.Register(new AgentTool( Name, "{{Escape(description)}}", _ => {{lambdaBody}}, category: ToolCategory.Addon)); } } """; File.WriteAllText(path, body, Encoding.UTF8); PatchAddonTools(root, className); return path; } private static string WriteSelfTool(string root, string toolName, string description, string invokeBody) { string className = ToPascalCase(toolName) + "Tool"; string dir = Path.Combine(root, "Lilith", "Tools", "Generated"); Directory.CreateDirectory(dir); string path = Path.Combine(dir, $"{className}.cs"); string lambdaBody = FormatLambdaBody(invokeBody); string body = $$""" using Agent.Core.ToolCalling; namespace Lilith.Agent.Tools.Generated; /// <summary>Generated Lilith self tool — {{description}}</summary> public static class {{className}} { public const string Name = "{{toolName}}"; public static void Register(ToolRegistry registry) { registry.Register(new AgentTool( Name, "{{Escape(description)}}", _ => {{lambdaBody}}, category: ToolCategory.Self)); } } """; File.WriteAllText(path, body, Encoding.UTF8); PatchLilithTools(root, className); return path; } private static void PatchBuiltInTools(string root, string className) { string path = Path.Combine(root, "Agent-Core", "Ollama", "ToolCalling", "BuiltInTools.cs"); const string marker = "// GENERATED_CORE_TOOLS"; string line = $" Agent.Core.ToolCalling.Generated.{className}.Register(registry);"; PatchRegistrar(path, marker, line, "using Agent.Core.ToolCalling.Generated;"); } private static void PatchAddonTools(string root, string className) { string path = Path.Combine(root, "Agent-Addons", "AddonTools.cs"); const string marker = "// GENERATED_ADDON_TOOLS"; string line = $" Agent.Addons.Tools.Generated.{className}.Register(registry);"; PatchRegistrar(path, marker, line, "using Agent.Addons.Tools.Generated;"); } private static void PatchLilithTools(string root, string className) { string path = Path.Combine(root, "Lilith", "Tools", "Lilith.Tools.cs"); const string marker = "// GENERATED_SELF_TOOLS"; string line = $" {className}.Register(registry);"; PatchRegistrar(path, marker, line, "using Lilith.Agent.Tools.Generated;"); } private static void PatchRegistrar(string filePath, string marker, string registerLine, string usingLine) { if (!File.Exists(filePath)) throw new FileNotFoundException($"Expected registrar file: {filePath}"); string text = File.ReadAllText(filePath); if (!text.Contains(marker, StringComparison.Ordinal)) { return; } if (!text.Contains(usingLine, StringComparison.Ordinal)) { int firstUsingEnd = text.IndexOf("using ", StringComparison.Ordinal); if (firstUsingEnd >= 0) { int lineEnd = text.IndexOf('\n', firstUsingEnd); text = text.Insert(lineEnd + 1, usingLine + Environment.NewLine); } } if (!text.Contains(registerLine, StringComparison.Ordinal)) { int markerIndex = text.IndexOf(marker, StringComparison.Ordinal); if (markerIndex < 0) return; int insertAt = text.IndexOf('\n', markerIndex) + 1; text = text.Insert(insertAt, registerLine + Environment.NewLine); } File.WriteAllText(filePath, text, Encoding.UTF8); } private static string Escape(string s) => s.Replace("\\", "\\\\").Replace("\"", "\\\""); /// <summary>Turns invoke_body like <c>return "pong";</c> into a valid lambda expression body.</summary> private static string FormatLambdaBody(string invokeBody) { if (string.IsNullOrWhiteSpace(invokeBody)) return "\"OK\""; string trimmed = invokeBody.Trim(); while (trimmed.EndsWith(';')) trimmed = trimmed[..^1].Trim(); if (trimmed.StartsWith("return ", StringComparison.OrdinalIgnoreCase)) { string expr = trimmed["return ".Length..].Trim(); while (expr.EndsWith(';')) expr = expr[..^1].Trim(); return string.IsNullOrWhiteSpace(expr) ? "\"OK\"" : expr; } if (trimmed.Contains('{') || trimmed.Contains('\n')) return $"{{ {trimmed}; }}"; return trimmed; } private static string ToPascalCase(string snake) { var parts = snake.Split('_', StringSplitOptions.RemoveEmptyEntries); var sb = new StringBuilder(); foreach (string part in parts) { if (part.Length == 0) continue; sb.Append(char.ToUpperInvariant(part[0])); if (part.Length > 1) sb.Append(part[1..]); } return sb.ToString(); } public static string ParseGenerateArgs(string argumentsJson, out string toolName, out string description, out string category, out string? invokeBody) { using var doc = JsonDocument.Parse(string.IsNullOrWhiteSpace(argumentsJson) ? "{}" : argumentsJson); var root = doc.RootElement; toolName = root.TryGetProperty("tool_name", out var toolEl) ? toolEl.GetString() ?? "" : ""; description = root.TryGetProperty("description", out var descEl) ? descEl.GetString() ?? "" : ""; category = root.TryGetProperty("category", out var catEl) ? catEl.GetString() ?? "self" : "self"; invokeBody = root.TryGetProperty("invoke_body", out var bodyEl) ? bodyEl.GetString() : null; return toolName; }}
Documentation
Lilith self-improvement (Version 7+)
Lilith can extend herself only by creating tools in three categories:
| Category | Location | Examples | |----------|----------|----------| | Core | Agent-Core | Memory, time (rare; system-level) | | Addon | Agent-Addons | Weather, C# projects, apps | | Self | Lilith | Workspace files, self-improvement |
Layout
ShippedSource/— copy ofsrc/Version7next to the built app (MSBuildCopyShippedSource){workspace}/output/self-improvement/live— current editable source{workspace}/output/self-improvement/sandbox— clone for edits and builds{workspace}/output/self-improvement/backups/{timestamp}— backups before promote
Tools
1. self_improve_get_source_layout 2. self_improve_generate_tool 3. self_improve_backup_live 4. self_improve_create_sandbox 5. self_improve_build_sandbox 6. self_improve_verify_sandbox_tool 7. self_improve_promote_sandbox — copies sandbox → live, builds, restarts
Set GENESIS_REPO_ROOT to the repo root when developing from source instead of ShippedSource.