Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Both versions exist purely as research projects used to explore new ideas and ga

# What’s next

An important aspect of KM² is how we are building the next memory prototype. In parallel, our team is developing [Amplifier](https://github.com/microsoft/amplifier/tree/next), a platform for metacognitive AI engineering. We use Amplifier to build Amplifier itself — and in the same way, we are using Amplifier to build the next generation of Kernel Memory.
An important aspect of KM² is how we are building the next memory prototype. In parallel, our team is developing [Amplifier](https://github.com/microsoft/amplifier), a platform for metacognitive AI engineering. We use Amplifier to build Amplifier itself — and in a similar way, we are using AI and Amplifier concepts to build the next generation of Kernel Memory.

KM² will focus on the following areas, which will be documented in more detail when ready:
- quality of content generated
Expand Down Expand Up @@ -76,4 +76,4 @@ gh api repos/:owner/:repo/contributors --paginate --jq '
| <img alt="Valkozaur" src="https://avatars.githubusercontent.com/u/58659526?v=4&s=110" width="110"> | <img alt="vicperdana" src="https://avatars.githubusercontent.com/u/7114832?v=4&s=110" width="110"> | <img alt="walexee" src="https://avatars.githubusercontent.com/u/12895846?v=4&s=110" width="110"> | <img alt="aportillo83" src="https://avatars.githubusercontent.com/u/72951744?v=4&s=110" width="110"> | <img alt="carlodek" src="https://avatars.githubusercontent.com/u/56030624?v=4&s=110" width="110"> | <img alt="KSemenenko" src="https://avatars.githubusercontent.com/u/4385716?v=4&s=110" width="110"> |
| [Valkozaur](https://github.com/Valkozaur) | [vicperdana](https://github.com/vicperdana) | [walexee](https://github.com/walexee) | [aportillo83](https://github.com/aportillo83) | [carlodek](https://github.com/carlodek) | [KSemenenko](https://github.com/KSemenenko) |
| <img alt="roldengarm" src="https://avatars.githubusercontent.com/u/37638588?v=4&s=110" width="110"> | <img alt="snakex64" src="https://avatars.githubusercontent.com/u/39806655?v=4&s=110" width="110"> |
| [roldengarm](https://github.com/roldengarm) | [snakex64](https://github.com/snakex64) |
| [roldengarm](https://github.com/roldengarm) | [snakex64](https://github.com/snakex64) |
48 changes: 48 additions & 0 deletions src/Core/Storage/Models/ContentDtoWithNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.
namespace KernelMemory.Core.Storage.Models;

/// <summary>
/// Content DTO with node information included.
/// Used by CLI commands to show which node the content came from.
/// </summary>
public class ContentDtoWithNode
{
public string Id { get; set; } = string.Empty;
public string Node { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public string MimeType { get; set; } = string.Empty;
public long ByteSize { get; set; }
public DateTimeOffset ContentCreatedAt { get; set; }
public DateTimeOffset RecordCreatedAt { get; set; }
public DateTimeOffset RecordUpdatedAt { get; set; }
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1819:Properties should not return arrays")]
public string[] Tags { get; set; } = [];
public Dictionary<string, string> Metadata { get; set; } = new();

/// <summary>
/// Creates a ContentDtoWithNode from a ContentDto and node ID.
/// </summary>
/// <param name="content">The content DTO to wrap.</param>
/// <param name="nodeId">The node ID to include.</param>
/// <returns>A new ContentDtoWithNode instance.</returns>
public static ContentDtoWithNode FromContentDto(ContentDto content, string nodeId)
{
return new ContentDtoWithNode
{
Id = content.Id,
Node = nodeId,
Content = content.Content,
MimeType = content.MimeType,
ByteSize = content.ByteSize,
ContentCreatedAt = content.ContentCreatedAt,
RecordCreatedAt = content.RecordCreatedAt,
RecordUpdatedAt = content.RecordUpdatedAt,
Title = content.Title,
Description = content.Description,
Tags = content.Tags,
Metadata = content.Metadata
};
}
}
20 changes: 2 additions & 18 deletions src/Main/CLI/Commands/GetCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,8 @@ public override async Task<int> ExecuteAsync(
}

// Wrap result with node information
var response = new
{
id = result.Id,
node = node.Id,
content = result.Content,
mimeType = result.MimeType,
byteSize = result.ByteSize,
contentCreatedAt = result.ContentCreatedAt,
recordCreatedAt = result.RecordCreatedAt,
recordUpdatedAt = result.RecordUpdatedAt,
title = result.Title,
description = result.Description,
tags = result.Tags,
metadata = result.Metadata
};

// If --full flag is set, ensure verbose mode for human formatter
// For JSON/YAML, all fields are always included
var response = Core.Storage.Models.ContentDtoWithNode.FromContentDto(result, node.Id);

formatter.Format(response);

return Constants.ExitCodeSuccess;
Expand Down
17 changes: 2 additions & 15 deletions src/Main/CLI/Commands/ListCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,8 @@ public override async Task<int> ExecuteAsync(
var items = await service.ListAsync(settings.Skip, settings.Take, CancellationToken.None).ConfigureAwait(false);

// Wrap items with node information
var itemsWithNode = items.Select(item => new
{
id = item.Id,
node = node.Id,
content = item.Content,
mimeType = item.MimeType,
byteSize = item.ByteSize,
contentCreatedAt = item.ContentCreatedAt,
recordCreatedAt = item.RecordCreatedAt,
recordUpdatedAt = item.RecordUpdatedAt,
title = item.Title,
description = item.Description,
tags = item.Tags,
metadata = item.Metadata
});
var itemsWithNode = items.Select(item =>
Core.Storage.Models.ContentDtoWithNode.FromContentDto(item, node.Id));

// Format list with pagination info
formatter.FormatList(itemsWithNode, totalCount, settings.Skip, settings.Take);
Expand Down
126 changes: 125 additions & 1 deletion src/Main/CLI/OutputFormatters/HumanOutputFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public void Format(object data)

switch (data)
{
case Core.Storage.Models.ContentDtoWithNode contentWithNode:
this.FormatContentWithNode(contentWithNode);
break;
case ContentDto content:
this.FormatContent(content);
break;
Expand Down Expand Up @@ -82,7 +85,11 @@ public void FormatList<T>(IEnumerable<T> items, long totalCount, int skip, int t

var itemsList = items.ToList();

if (typeof(T) == typeof(ContentDto))
if (typeof(T) == typeof(Core.Storage.Models.ContentDtoWithNode))
{
this.FormatContentWithNodeList(itemsList.Cast<Core.Storage.Models.ContentDtoWithNode>(), totalCount, skip, take);
}
else if (typeof(T) == typeof(ContentDto))
{
this.FormatContentList(itemsList.Cast<ContentDto>(), totalCount, skip, take);
}
Expand Down Expand Up @@ -278,4 +285,121 @@ private void FormatGenericList<T>(IEnumerable<T> items, long totalCount, int ski
AnsiConsole.WriteLine(item?.ToString() ?? string.Empty);
}
}

private void FormatContentWithNode(Core.Storage.Models.ContentDtoWithNode content)
{
var isQuiet = this.Verbosity.Equals("quiet", StringComparison.OrdinalIgnoreCase);
var isVerbose = this.Verbosity.Equals("verbose", StringComparison.OrdinalIgnoreCase);

if (isQuiet)
{
// Quiet mode: just the ID
AnsiConsole.WriteLine(content.Id);
return;
}

var table = new Table();
table.Border(TableBorder.Rounded);
table.AddColumn("Property");
table.AddColumn("Value");

table.AddRow("[yellow]Node[/]", Markup.Escape(content.Node));
table.AddRow("[yellow]ID[/]", Markup.Escape(content.Id));

// Truncate content unless verbose
var displayContent = content.Content;
if (!isVerbose && displayContent.Length > Constants.MaxContentDisplayLength)
{
displayContent = string.Concat(displayContent.AsSpan(0, Constants.MaxContentDisplayLength), "...");
}
table.AddRow("[yellow]Content[/]", Markup.Escape(displayContent));

if (!string.IsNullOrEmpty(content.Title))
{
table.AddRow("[yellow]Title[/]", Markup.Escape(content.Title));
}

if (!string.IsNullOrEmpty(content.Description))
{
table.AddRow("[yellow]Description[/]", Markup.Escape(content.Description));
}

if (content.Tags.Length > 0)
{
table.AddRow("[yellow]Tags[/]", Markup.Escape(string.Join(", ", content.Tags)));
}

if (isVerbose)
{
table.AddRow("[yellow]MimeType[/]", Markup.Escape(content.MimeType));
table.AddRow("[yellow]Size[/]", $"{content.ByteSize} bytes");
table.AddRow("[yellow]ContentCreatedAt[/]", content.ContentCreatedAt.ToString("O"));
table.AddRow("[yellow]RecordCreatedAt[/]", content.RecordCreatedAt.ToString("O"));
table.AddRow("[yellow]RecordUpdatedAt[/]", content.RecordUpdatedAt.ToString("O"));

if (content.Metadata.Count > 0)
{
var metadataStr = string.Join(", ", content.Metadata.Select(kvp => $"{kvp.Key}={kvp.Value}"));
table.AddRow("[yellow]Metadata[/]", Markup.Escape(metadataStr));
}
}

AnsiConsole.Write(table);
}

private void FormatContentWithNodeList(IEnumerable<Core.Storage.Models.ContentDtoWithNode> contents, long totalCount, int skip, int take)
{
var isQuiet = this.Verbosity.Equals("quiet", StringComparison.OrdinalIgnoreCase);
var contentsList = contents.ToList();

// Check if list is empty
if (contentsList.Count == 0)
{
if (this._useColors)
{
AnsiConsole.MarkupLine("[dim]No content found[/]");
}
else
{
AnsiConsole.WriteLine("No content found");
}
return;
}

if (isQuiet)
{
// Quiet mode: just IDs
foreach (var content in contentsList)
{
AnsiConsole.WriteLine(content.Id);
}
return;
}

// Show pagination info
AnsiConsole.MarkupLine($"[cyan]Showing {contentsList.Count} of {totalCount} items (skip: {skip})[/]");
AnsiConsole.WriteLine();

// Create table
var table = new Table();
table.Border(TableBorder.Rounded);
table.AddColumn("[yellow]Node[/]");
table.AddColumn("[yellow]ID[/]");
table.AddColumn("[yellow]Content Preview[/]");

foreach (var content in contentsList)
{
var preview = content.Content.Length > 50
? string.Concat(content.Content.AsSpan(0, 50), "...")
: content.Content;

table.AddRow(
Markup.Escape(content.Node),
Markup.Escape(content.Id),
Markup.Escape(preview)
);
}

AnsiConsole.Write(table);
}
}
Loading