AI/Agents/AgentThought.cs
using System;
using System.Text.Json.Nodes;
namespace HC3;
/// <summary>
/// Essentially a log of an agent's thoughts and experiences.
/// </summary>
public class AgentThought
{
/// <summary>
/// When this thought occurred
/// </summary>
public DateTimeOffset TimeStamp { get; set; }
/// <summary>
/// The message describing what the agent thought/experienced
/// </summary>
public string Message { get; set; }
/// <summary>
/// Optional world position where this thought occurred
/// </summary>
public Vector3? Location { get; set; }
/// <summary>
/// Tags associated with this thought (e.g. "good", "bad", "purchase", "hunt", etc)
/// </summary>
public HashSet<string> Tags { get; set; } = new();
public AgentThought( string message )
{
TimeStamp = DateTimeOffset.UtcNow;
Message = message;
}
// Add serialization support
public JsonObject ToJson()
{
var json = new JsonObject
{
["timestamp"] = TimeStamp.ToString( "O" ),
["message"] = Message,
["tags"] = new JsonArray( Tags.Select( t => JsonValue.Create( t ) ).ToArray() )
};
if ( Location.HasValue )
{
json["location"] = Location.Value.ToString();
}
return json;
}
public static AgentThought FromJson( JsonNode node )
{
if ( node == null ) return null;
var thought = new AgentThought( node["message"]?.GetValue<string>() ?? "" )
{
TimeStamp = DateTimeOffset.Parse( node["timestamp"]?.GetValue<string>() ?? DateTimeOffset.UtcNow.ToString( "O" ) )
};
if ( node["location"] != null )
{
thought.Location = Vector3.Parse( node["location"].GetValue<string>() );
}
if ( node["tags"] is JsonArray tags )
{
foreach ( var tag in tags )
{
thought.Tags.Add( tag.GetValue<string>() );
}
}
return thought;
}
}
/// <summary>
/// Component that tracks an agent's recent thoughts and experiences
/// </summary>
public class AgentThoughtLog : Component
{
/// <summary>
/// Maximum number of thought entries to keep in history
/// </summary>
[Property] public int MaxEntries { get; set; } = 10;
/// <summary>
/// List of recent thoughts, most recent first
/// </summary>
private List<AgentThought> _thoughts = new();
public IReadOnlyList<AgentThought> GetAll() => _thoughts;
/// <summary>
/// Number of thoughts to save to persistence
/// </summary>
private const int SavedThoughtCount = 3;
/// <summary>
/// Add a new thought entry to the log
/// </summary>
public AgentThought AddThought( string message, Vector3? location = null )
{
var thought = new AgentThought( message );
if ( location.HasValue )
{
thought.Location = location.Value;
}
_thoughts.Insert( 0, thought );
// Trim to max entries
while ( _thoughts.Count > MaxEntries )
{
_thoughts.RemoveAt( _thoughts.Count - 1 );
}
return thought;
}
/// <summary>
/// Get most recent thoughts with specific tags
/// </summary>
public IEnumerable<AgentThought> GetThoughtsByTags( params string[] tags )
{
return _thoughts.Where( t => tags.All( tag => t.Tags.Contains( tag ) ) );
}
/// <summary>
/// Clear all logged thoughts
/// </summary>
public void Clear()
{
_thoughts.Clear();
}
/// <summary>
/// Serialize the last few thoughts to JSON for persistence
/// </summary>
public JsonObject ToJson()
{
var thoughts = new JsonArray();
foreach ( var thought in _thoughts.Take( SavedThoughtCount ) )
{
thoughts.Add( thought.ToJson() );
}
var json = new JsonObject
{
["thoughts"] = thoughts
};
return json;
}
/// <summary>
/// Load thoughts from JSON persistence data
/// </summary>
public void FromJson( JsonNode node )
{
if ( node?["thoughts"] is JsonArray thoughts )
{
_thoughts.Clear();
foreach ( var thoughtNode in thoughts )
{
var thought = AgentThought.FromJson( thoughtNode );
if ( thought != null )
{
_thoughts.Add( thought );
}
}
}
}
}