ToolSpecStore.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.Text.Json;using System.Text.RegularExpressions;using Agent.Core;namespace Lilith.Agent.SelfImprovement;/// <summary>Persists the tool-under-test spec for self-improvement smoke runs (edit scenario step 1 only).</summary>internal sealed class ToolSpecStore{    public const string FileName = "self-improvement-tool-spec.json";    public string ToolName { get; set; } = "";    public string Description { get; set; } = "";    public string Category { get; set; } = "self";    public string InvokeBody { get; set; } = "return \"OK\";";    public string ExpectedResult { get; set; } = "";    public static string GetSpecPath(Workspace workspace) =>        Path.Combine(workspace.ConfigFolder, FileName);    public static ToolSpecStore Load(Workspace workspace)    {        string path = GetSpecPath(workspace);        if (!File.Exists(path))            return new ToolSpecStore();        string json = File.ReadAllText(path);        return JsonSerializer.Deserialize<ToolSpecStore>(json, JsonOptions) ?? new ToolSpecStore();    }    public void Save(Workspace workspace)    {        Directory.CreateDirectory(workspace.ConfigFolder);        File.WriteAllText(GetSpecPath(workspace), JsonSerializer.Serialize(this, JsonOptions));    }    public static ToolSpecStore ParseFromJson(string argumentsJson)    {        using var doc = JsonDocument.Parse(string.IsNullOrWhiteSpace(argumentsJson) ? "{}" : argumentsJson);        var root = doc.RootElement;        return new ToolSpecStore        {            ToolName = GetString(root, "tool_name", "name", "tool"),            Description = GetString(root, "description", "desc"),            Category = GetString(root, "category", "cat") is { Length: > 0 } c ? c : "self",            InvokeBody = GetString(root, "invoke_body", "invokeBody", "body"),            ExpectedResult = GetString(root, "expected_result", "expectedResult", "expected"),        };    }    /// <summary>Expected tool return value for smoke invoke checks (explicit or parsed from invoke_body).</summary>    public string? ResolveExpectedResult()    {        if (!string.IsNullOrWhiteSpace(ExpectedResult))            return ExpectedResult.Trim();        return TryParseReturnLiteral(InvokeBody);    }    public static string? TryParseReturnLiteral(string? invokeBody)    {        if (string.IsNullOrWhiteSpace(invokeBody))            return null;        var match = Regex.Match(invokeBody.Trim(), @"return\s+""([^""]*)""\s*;?", RegexOptions.IgnoreCase);        return match.Success ? match.Groups[1].Value : null;    }    public void MergeMissing(ToolSpecStore other)    {        if (string.IsNullOrWhiteSpace(ToolName) && !string.IsNullOrWhiteSpace(other.ToolName))            ToolName = other.ToolName;        if (string.IsNullOrWhiteSpace(Description) && !string.IsNullOrWhiteSpace(other.Description))            Description = other.Description;        if (string.IsNullOrWhiteSpace(Category) && !string.IsNullOrWhiteSpace(other.Category))            Category = other.Category;        if (string.IsNullOrWhiteSpace(InvokeBody) && !string.IsNullOrWhiteSpace(other.InvokeBody))            InvokeBody = other.InvokeBody;    }    public bool IsComplete =>        !string.IsNullOrWhiteSpace(ToolName)        && !string.IsNullOrWhiteSpace(Description)        && !string.IsNullOrWhiteSpace(Category);    private static string GetString(JsonElement root, params string[] names)    {        foreach (string name in names)        {            if (root.TryGetProperty(name, out var el))            {                string? s = el.GetString();                if (!string.IsNullOrWhiteSpace(s))                    return s.Trim();            }        }        return "";    }    private static readonly JsonSerializerOptions JsonOptions = new()    {        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,        WriteIndented = true,    };}