using System;using System.IO;using System.Text.Json;using Agent.Core.Logging;using Agent.Core.ToolCalling;namespace Lilith.Agent;public partial class Lilith{ public void RegisterWorkspaceTools(ToolRegistry registry) { registry.Register(new AgentTool( "write_workspace_file", "Writes a project/document file to the workspace output folder. Do NOT use for user facts — use store_memory with paths like user/name instead.", args => WriteWorkspaceFile(args), new { type = "object", properties = new { path = new { type = "string", description = "Relative path/filename of the file to write in the output folder." }, content = new { type = "string", description = "The text content to write into the file." } }, required = new[] { "path", "content" } } )); registry.Register(new AgentTool( "read_workspace_file", "Reads a project/document file from the workspace (output, then config). Do NOT use for user facts — use retrieve_memory or get_memory_at_path.", args => ReadWorkspaceFile(args), new { type = "object", properties = new { path = new { type = "string", description = "Relative path of the file to read." } }, required = new[] { "path" } } )); registry.Register(new AgentTool( "list_workspace_files", "Lists all files and subdirectories located inside both the config and output workspace folders.", _ => ListWorkspaceFiles(), null )); } private string WriteWorkspaceFile(string argumentsJson) { if (Workspace is null) return "Error: Workspace is not initialized."; try { using var doc = JsonDocument.Parse(argumentsJson); var root = doc.RootElement; if (!root.TryGetProperty("path", out var pathProp) || !root.TryGetProperty("content", out var contentProp)) { return "Error: Missing path or content properties."; } string relativePath = pathProp.GetString() ?? ""; string content = contentProp.GetString() ?? ""; if (string.IsNullOrWhiteSpace(relativePath)) { return "Error: Path cannot be empty."; } if (IsMemoryNamespacePath(relativePath)) { return StoreViaMemoryAsync(relativePath, content); } string? safePath = GetSafePath(Workspace.OutputFolder, relativePath); if (safePath is null) { return "Error: Access denied. Path is outside of output workspace directory."; } string? dir = Path.GetDirectoryName(safePath); if (dir is not null && !Directory.Exists(dir)) { Directory.CreateDirectory(dir); } File.WriteAllText(safePath, content, System.Text.Encoding.UTF8); return $"Success: Successfully wrote {content.Length} characters to file '{relativePath}'."; } catch (Exception ex) { return $"Error writing file: {ex.Message}"; } } private string ReadWorkspaceFile(string argumentsJson) { if (Workspace is null) return "Error: Workspace is not initialized."; try { using var doc = JsonDocument.Parse(argumentsJson); var root = doc.RootElement; if (!root.TryGetProperty("path", out var pathProp)) { return "Error: Missing path property."; } string relativePath = pathProp.GetString() ?? ""; if (string.IsNullOrWhiteSpace(relativePath)) { return "Error: Path cannot be empty."; } if (IsMemoryNamespacePath(relativePath)) { return ReadViaMemory(relativePath); } string? safeOutputPath = GetSafePath(Workspace.OutputFolder, relativePath); if (safeOutputPath is not null && File.Exists(safeOutputPath)) { return File.ReadAllText(safeOutputPath, System.Text.Encoding.UTF8); } string? safeConfigPath = GetSafePath(Workspace.ConfigFolder, relativePath); if (safeConfigPath is not null && File.Exists(safeConfigPath)) { return File.ReadAllText(safeConfigPath, System.Text.Encoding.UTF8); } return $"Error: File '{relativePath}' not found in output or config folders."; } catch (Exception ex) { return $"Error reading file: {ex.Message}"; } } private string ListWorkspaceFiles() { if (Workspace is null) return "Error: Workspace is not initialized."; var sb = new System.Text.StringBuilder(); sb.AppendLine("Workspace Files & Directories:"); sb.AppendLine(); sb.AppendLine("1. Setup & Configuration Folder (Config):"); if (Directory.Exists(Workspace.ConfigFolder)) { var files = Directory.GetFiles(Workspace.ConfigFolder, "*", SearchOption.AllDirectories); if (files.Length == 0) { sb.AppendLine(" (Empty)"); } foreach (var file in files) { var rel = Path.GetRelativePath(Workspace.ConfigFolder, file).Replace('\\', '/'); var size = new FileInfo(file).Length; sb.AppendLine($" - {rel} ({FormatSize(size)})"); } } else { sb.AppendLine(" (Directory does not exist)"); } sb.AppendLine(); sb.AppendLine("2. Output & Creative Sandbox Folder (Output):"); if (Directory.Exists(Workspace.OutputFolder)) { var files = Directory.GetFiles(Workspace.OutputFolder, "*", SearchOption.AllDirectories); if (files.Length == 0) { sb.AppendLine(" (Empty)"); } foreach (var file in files) { var rel = Path.GetRelativePath(Workspace.OutputFolder, file).Replace('\\', '/'); var size = new FileInfo(file).Length; sb.AppendLine($" - {rel} ({FormatSize(size)})"); } } else { sb.AppendLine(" (Directory does not exist)"); } return sb.ToString(); } private string StoreViaMemoryAsync(string path, string content) { if (_client?.Memory is null) { return "Error: Memory system is not initialized."; } try { Logger.WriteLine( "Memory", $"Storing to memory path '{path}' (redirected from write_workspace_file)...", LogColors.Cyan); _client.Memory.StoreAsync(path, content).GetAwaiter().GetResult(); return $"Success: Stored memory under path '{path}'."; } catch (Exception ex) { return $"Error: {ex.Message}"; } } private string ReadViaMemory(string path) { if (_client?.Memory is null) { return "Error: Memory system is not initialized."; } string? exact = _client.Memory.QueryPath(path); if (exact is not null) { return exact; } try { var matches = _client.Memory.RetrieveAsync(path, topN: 1).GetAwaiter().GetResult(); if (matches.Count > 0) { return matches[0].Value; } } catch (Exception ex) { return $"Error: {ex.Message}"; } return $"No memory found at path '{path}'."; } private static bool IsMemoryNamespacePath(string relativePath) { string normalized = relativePath.Replace('\\', '/').TrimStart('/'); return normalized.StartsWith("user/", StringComparison.OrdinalIgnoreCase) || string.Equals(normalized, "user", StringComparison.OrdinalIgnoreCase); } private static string? GetSafePath(string baseFolder, string relativePath) { if (string.IsNullOrWhiteSpace(baseFolder)) return null; string fullBase = Path.GetFullPath(baseFolder); string combined = Path.GetFullPath(Path.Combine(fullBase, relativePath)); if (combined.StartsWith(fullBase, StringComparison.OrdinalIgnoreCase)) { return combined; } return null; } private static string FormatSize(long bytes) { if (bytes >= 1024 * 1024) return $"{bytes / 1024.0 / 1024.0:F2} MB"; if (bytes >= 1024) return $"{bytes / 1024.0:F2} KB"; return $"{bytes} bytes"; }}
Documentation
Lilith (Version 3)
Version 3 Lilith agent library. Entry point: Lilith.cs with partials under Boot/, Chat/, Ollama/, and SystemPrompt/.
Build the solution or run python ../Build-v3.py from this tree.