SelfImprovement/ToolScaffoldGenerator.cscsharp

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 of src/Version7 next to the built app (MSBuild CopyShippedSource)
  • {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.

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;    }}