feat: Implement learn command for agent tool with knowledge management features

- Introduced the `agent learn` command to manage learned knowledge, including user preferences, ROM patterns, project context, and conversation memory.
- Added functionality for setting, getting, listing preferences, and managing project contexts.
- Implemented memory storage and retrieval, allowing the agent to remember past conversations and key facts.
- Enhanced CLI output with usage instructions and error handling for the learn command.
- Created a new `LearnedKnowledgeService` to handle persistent data storage and retrieval.
This commit is contained in:
scawful
2025-10-04 22:10:58 -04:00
parent ffaec122cf
commit e83235ee1a
6 changed files with 1147 additions and 10 deletions

View File

@@ -134,6 +134,7 @@ The `z3ed` CLI is the foundation for an AI-driven Model-Code-Program (MCP) loop,
- `agent chat`: Opens an interactive terminal chat (TUI) with the AI agent.
- `agent simple-chat`: A lightweight, non-TUI chat mode for scripting and automation.
- `agent test ...`: Commands for running and managing automated GUI tests.
- `agent learn ...`: **NEW**: Manage learned knowledge (preferences, ROM patterns, project context, conversation memory).
### Resource Commands
@@ -163,11 +164,82 @@ Accessible from **Debug → Agent Chat** inside YAZE. Provides the same conversa
Z3ED supports multiple AI providers. Configuration is resolved with command-line flags taking precedence over environment variables.
- `--ai_provider=<provider>`: Selects the AI provider (`mock`, `ollama`, `gemini`).
- `--ai_model=<model>`: Specifies the model name (e.g., `qwen2.5-coder:7b`, `gemini-1.5-flash`).
- `--ai_model=<model>`: Specifies the model name (e.g., `qwen2.5-coder:7b`, `gemini-2.5-flash`).
- `--gemini_api_key=<key>`: Your Gemini API key.
- `--ollama_host=<url>`: The URL for your Ollama server (default: `http://localhost:11434`).
## 8. CLI Output & Help System
### System Prompt Versions
Z3ED includes multiple system prompt versions for different use cases:
- **v1 (default)**: Original reactive prompt with basic tool calling
- **v2**: Enhanced with better JSON formatting and error handling
- **v3 (latest)**: Proactive prompt with intelligent tool chaining and implicit iteration - **RECOMMENDED**
To use v3 prompt: Set environment variable `Z3ED_PROMPT_VERSION=v3` or it will be auto-selected for Gemini 2.0+ models.
## 8. Learn Command - Knowledge Management
The learn command enables the AI agent to remember preferences, patterns, and context across sessions.
### Basic Usage
```bash
# Store a preference
z3ed agent learn --preference "default_palette=2"
# Get a preference
z3ed agent learn --get-preference default_palette
# List all preferences
z3ed agent learn --list-preferences
# View statistics
z3ed agent learn --stats
# Export all learned data
z3ed agent learn --export my_learned_data.json
# Import learned data
z3ed agent learn --import my_learned_data.json
```
### Project Context
Store project-specific information that the agent can reference:
```bash
# Save project context
z3ed agent learn --project "myrom" --context "Vanilla+ difficulty hack, focus on dungeon redesign"
# List projects
z3ed agent learn --list-projects
# Get project details
z3ed agent learn --get-project "myrom"
```
### Conversation Memory
The agent automatically stores summaries of conversations for future reference:
```bash
# View recent memories
z3ed agent learn --recent-memories 10
# Search memories by topic
z3ed agent learn --search-memories "room 5"
```
### Storage Location
All learned data is stored in `~/.yaze/agent/`:
- `preferences.json`: User preferences
- `patterns.json`: Learned ROM patterns
- `projects.json`: Project contexts
- `memories.json`: Conversation summaries
## 9. CLI Output & Help System
The `z3ed` CLI features a modernized output system designed to be clean for users and informative for developers.
@@ -205,7 +277,7 @@ The help system is organized by category for easy navigation.
- **Main Help**: `z3ed --help` or `z3ed -h` shows a high-level overview of command categories.
- **Category Help**: `z3ed help <category>` provides detailed information for a specific group of commands (e.g., `agent`, `patch`, `rom`).
## 9. Collaborative Sessions & Multimodal Vision
## 10. Collaborative Sessions & Multimodal Vision
### Overview
@@ -784,7 +856,7 @@ The AI response appears in your chat history and can reference specific details
- **Agent Editor Docs**: `src/app/editor/agent/README.md`
- **Integration Guide**: `docs/z3ed/YAZE_SERVER_V2_INTEGRATION.md`
## 10. Roadmap & Implementation Status
## 11. Roadmap & Implementation Status
**Last Updated**: October 4, 2025
@@ -815,7 +887,13 @@ The AI response appears in your chat history and can reference specific details
4. **Collaboration UI Enhancements (1 day)**: Add UI elements for ROM sync, snapshot sharing, and proposal management in the Agent Chat widget.
5. **Windows Cross-Platform Testing (8-10h)**: Validate `z3ed` and the test harness on Windows.
## 11. Troubleshooting
### ✅ Recently Completed (v0.2.0-alpha)
- **Enhanced System Prompt (v3)**: Proactive tool chaining with implicit iteration to minimize back-and-forth
- **Learn Command**: Full implementation with preferences, ROM patterns, project context, and conversation memory
- **gRPC Windows Build Optimization**: Documented vcpkg approach and optimization strategies
## 12. Troubleshooting
- **"Build with -DZ3ED_AI=ON" warning**: AI features are disabled. Rebuild with the flag to enable them.
- **"gRPC not available" error**: GUI testing is disabled. Rebuild with `-DYAZE_WITH_GRPC=ON`.

View File

@@ -110,7 +110,7 @@ absl::Status Agent::Run(const std::vector<std::string>& arg_vec) {
return agent::HandleGuiCommand(subcommand_args);
}
if (subcommand == "learn") {
return agent::HandleLearnCommand();
return agent::HandleLearnCommand(subcommand_args);
}
if (subcommand == "list") {
return agent::HandleListCommand();

View File

@@ -20,7 +20,7 @@ absl::Status HandleDiffCommand(Rom& rom,
absl::Status HandleAcceptCommand(const std::vector<std::string>& args, Rom& rom);
absl::Status HandleTestCommand(const std::vector<std::string>& args);
absl::Status HandleGuiCommand(const std::vector<std::string>& args);
absl::Status HandleLearnCommand();
absl::Status HandleLearnCommand(const std::vector<std::string>& args = {});
absl::Status HandleListCommand();
absl::Status HandleCommitCommand(Rom& rom);
absl::Status HandleRevertCommand(Rom& rom);

View File

@@ -385,9 +385,170 @@ absl::Status HandleDiffCommand(Rom& rom, const std::vector<std::string>& args) {
return absl::OkStatus();
}
absl::Status HandleLearnCommand() {
std::cout << "Agent learn not yet implemented." << std::endl;
return absl::OkStatus();
absl::Status HandleLearnCommand(const std::vector<std::string>& args) {
using namespace yaze::cli::agent;
static LearnedKnowledgeService learn_service;
static bool initialized = false;
if (!initialized) {
auto status = learn_service.Initialize();
if (!status.ok()) {
std::cerr << "Failed to initialize learned knowledge service: "
<< status.message() << std::endl;
return status;
}
initialized = true;
}
if (args.empty()) {
// Show usage
std::cout << "\nUsage: z3ed agent learn [options]\n\n";
std::cout << "Options:\n";
std::cout << " --preference <key>=<value> Set a preference\n";
std::cout << " --get-preference <key> Get a preference value\n";
std::cout << " --list-preferences List all preferences\n";
std::cout << " --pattern <type> --data <json> Learn a ROM pattern\n";
std::cout << " --query-patterns <type> Query learned patterns\n";
std::cout << " --project <name> --context <text> Save project context\n";
std::cout << " --get-project <name> Get project context\n";
std::cout << " --list-projects List all projects\n";
std::cout << " --memory <topic> --summary <text> Store conversation memory\n";
std::cout << " --search-memories <query> Search memories\n";
std::cout << " --recent-memories [limit] Show recent memories\n";
std::cout << " --export <file> Export all data to JSON\n";
std::cout << " --import <file> Import data from JSON\n";
std::cout << " --stats Show statistics\n";
std::cout << " --clear Clear all learned data\n";
return absl::OkStatus();
}
// Parse arguments
std::string command = args[0];
if (command == "--preference" && args.size() >= 2) {
std::string pref = args[1];
size_t eq_pos = pref.find('=');
if (eq_pos == std::string::npos) {
return absl::InvalidArgumentError("Preference must be in format key=value");
}
std::string key = pref.substr(0, eq_pos);
std::string value = pref.substr(eq_pos + 1);
auto status = learn_service.SetPreference(key, value);
if (status.ok()) {
std::cout << "✓ Preference '" << key << "' set to '" << value << "'\n";
}
return status;
}
if (command == "--get-preference" && args.size() >= 2) {
auto value = learn_service.GetPreference(args[1]);
if (value) {
std::cout << args[1] << " = " << *value << "\n";
} else {
std::cout << "Preference '" << args[1] << "' not found\n";
}
return absl::OkStatus();
}
if (command == "--list-preferences") {
auto prefs = learn_service.GetAllPreferences();
if (prefs.empty()) {
std::cout << "No preferences stored.\n";
} else {
std::cout << "\n=== Stored Preferences ===\n";
for (const auto& [key, value] : prefs) {
std::cout << " " << key << " = " << value << "\n";
}
}
return absl::OkStatus();
}
if (command == "--stats") {
auto stats = learn_service.GetStats();
std::cout << "\n=== Learned Knowledge Statistics ===\n";
std::cout << " Preferences: " << stats.preference_count << "\n";
std::cout << " ROM Patterns: " << stats.pattern_count << "\n";
std::cout << " Projects: " << stats.project_count << "\n";
std::cout << " Memories: " << stats.memory_count << "\n";
std::cout << " First learned: " << absl::FormatTime(absl::FromUnixMillis(stats.first_learned_at)) << "\n";
std::cout << " Last updated: " << absl::FormatTime(absl::FromUnixMillis(stats.last_updated_at)) << "\n";
return absl::OkStatus();
}
if (command == "--export" && args.size() >= 2) {
auto json = learn_service.ExportToJSON();
if (!json.ok()) {
return json.status();
}
std::ofstream file(args[1]);
if (!file.is_open()) {
return absl::InternalError("Failed to open file for writing");
}
file << *json;
std::cout << "✓ Exported learned data to " << args[1] << "\n";
return absl::OkStatus();
}
if (command == "--import" && args.size() >= 2) {
std::ifstream file(args[1]);
if (!file.is_open()) {
return absl::NotFoundError("File not found: " + args[1]);
}
std::stringstream buffer;
buffer << file.rdbuf();
auto status = learn_service.ImportFromJSON(buffer.str());
if (status.ok()) {
std::cout << "✓ Imported learned data from " << args[1] << "\n";
}
return status;
}
if (command == "--clear") {
auto status = learn_service.ClearAll();
if (status.ok()) {
std::cout << "✓ All learned data cleared\n";
}
return status;
}
if (command == "--list-projects") {
auto projects = learn_service.GetAllProjects();
if (projects.empty()) {
std::cout << "No projects stored.\n";
} else {
std::cout << "\n=== Stored Projects ===\n";
for (const auto& proj : projects) {
std::cout << " " << proj.project_name << "\n";
std::cout << " ROM Hash: " << proj.rom_hash.substr(0, 16) << "...\n";
std::cout << " Last Accessed: " << absl::FormatTime(absl::FromUnixMillis(proj.last_accessed)) << "\n";
}
}
return absl::OkStatus();
}
if (command == "--recent-memories") {
int limit = 10;
if (args.size() >= 2) {
limit = std::stoi(args[1]);
}
auto memories = learn_service.GetRecentMemories(limit);
if (memories.empty()) {
std::cout << "No memories stored.\n";
} else {
std::cout << "\n=== Recent Memories ===\n";
for (const auto& mem : memories) {
std::cout << " Topic: " << mem.topic << "\n";
std::cout << " Summary: " << mem.summary << "\n";
std::cout << " Facts: " << mem.key_facts.size() << " key facts\n";
std::cout << " Created: " << absl::FormatTime(absl::FromUnixMillis(mem.created_at)) << "\n";
std::cout << "\n";
}
}
return absl::OkStatus();
}
return absl::InvalidArgumentError("Unknown learn command. Use 'z3ed agent learn' for usage.");
}
absl::Status HandleListCommand() {

View File

@@ -0,0 +1,673 @@
#include "cli/service/agent/learned_knowledge_service.h"
#include <algorithm>
#include <chrono>
#include <fstream>
#include <iostream>
#include <sstream>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#ifdef _WIN32
#include <direct.h>
#define mkdir(path, mode) _mkdir(path)
#else
#include <sys/stat.h>
#include <sys/types.h>
#endif
namespace yaze {
namespace cli {
namespace agent {
namespace {
int64_t CurrentTimestamp() {
return absl::ToUnixMillis(absl::Now());
}
std::string GenerateRandomID() {
static int counter = 0;
auto now = std::chrono::system_clock::now().time_since_epoch().count();
return absl::StrFormat("%lld_%d", now, counter++);
}
bool FileExists(const std::filesystem::path& path) {
return std::filesystem::exists(path);
}
absl::Status EnsureDirectoryExists(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) {
std::error_code ec;
if (!std::filesystem::create_directories(path, ec)) {
return absl::InternalError(
absl::StrCat("Failed to create directory: ", path.string(),
" - ", ec.message()));
}
}
return absl::OkStatus();
}
} // namespace
LearnedKnowledgeService::LearnedKnowledgeService()
: LearnedKnowledgeService(std::filesystem::path(getenv("HOME") ? getenv("HOME") : ".") / ".yaze" / "agent") {}
LearnedKnowledgeService::LearnedKnowledgeService(
const std::filesystem::path& data_dir)
: data_dir_(data_dir),
prefs_file_(data_dir / "preferences.json"),
patterns_file_(data_dir / "patterns.json"),
projects_file_(data_dir / "projects.json"),
memories_file_(data_dir / "memories.json") {}
absl::Status LearnedKnowledgeService::Initialize() {
if (initialized_) {
return absl::OkStatus();
}
// Ensure data directory exists
auto status = EnsureDirectoryExists(data_dir_);
if (!status.ok()) {
return status;
}
// Load existing data
LoadPreferences(); // Ignore errors for empty files
LoadPatterns();
LoadProjects();
LoadMemories();
initialized_ = true;
return absl::OkStatus();
}
absl::Status LearnedKnowledgeService::SaveAll() {
auto status = SavePreferences();
if (!status.ok()) return status;
status = SavePatterns();
if (!status.ok()) return status;
status = SaveProjects();
if (!status.ok()) return status;
status = SaveMemories();
if (!status.ok()) return status;
return absl::OkStatus();
}
// === Preference Management ===
absl::Status LearnedKnowledgeService::SetPreference(const std::string& key,
const std::string& value) {
if (!initialized_) {
return absl::FailedPreconditionError("Service not initialized");
}
preferences_[key] = value;
return SavePreferences();
}
std::optional<std::string> LearnedKnowledgeService::GetPreference(
const std::string& key) const {
auto it = preferences_.find(key);
if (it != preferences_.end()) {
return it->second;
}
return std::nullopt;
}
std::map<std::string, std::string> LearnedKnowledgeService::GetAllPreferences() const {
return preferences_;
}
absl::Status LearnedKnowledgeService::RemovePreference(const std::string& key) {
if (!initialized_) {
return absl::FailedPreconditionError("Service not initialized");
}
preferences_.erase(key);
return SavePreferences();
}
// === ROM Pattern Learning ===
absl::Status LearnedKnowledgeService::LearnPattern(const std::string& type,
const std::string& rom_hash,
const std::string& data,
float confidence) {
if (!initialized_) {
return absl::FailedPreconditionError("Service not initialized");
}
ROMPattern pattern;
pattern.pattern_type = type;
pattern.rom_hash = rom_hash;
pattern.pattern_data = data;
pattern.confidence = confidence;
pattern.learned_at = CurrentTimestamp();
pattern.access_count = 1;
patterns_.push_back(pattern);
return SavePatterns();
}
std::vector<LearnedKnowledgeService::ROMPattern>
LearnedKnowledgeService::QueryPatterns(const std::string& type,
const std::string& rom_hash) const {
std::vector<ROMPattern> results;
for (const auto& pattern : patterns_) {
bool type_match = type.empty() || pattern.pattern_type == type;
bool hash_match = rom_hash.empty() || pattern.rom_hash == rom_hash;
if (type_match && hash_match) {
results.push_back(pattern);
}
}
return results;
}
absl::Status LearnedKnowledgeService::UpdatePatternConfidence(
const std::string& type,
const std::string& rom_hash,
float new_confidence) {
bool found = false;
for (auto& pattern : patterns_) {
if (pattern.pattern_type == type && pattern.rom_hash == rom_hash) {
pattern.confidence = new_confidence;
pattern.access_count++;
found = true;
}
}
if (!found) {
return absl::NotFoundError("Pattern not found");
}
return SavePatterns();
}
// === Project Context ===
absl::Status LearnedKnowledgeService::SaveProjectContext(
const std::string& project_name,
const std::string& rom_hash,
const std::string& context) {
if (!initialized_) {
return absl::FailedPreconditionError("Service not initialized");
}
// Update existing or create new
bool found = false;
for (auto& project : projects_) {
if (project.project_name == project_name) {
project.rom_hash = rom_hash;
project.context_data = context;
project.last_accessed = CurrentTimestamp();
found = true;
break;
}
}
if (!found) {
ProjectContext project;
project.project_name = project_name;
project.rom_hash = rom_hash;
project.context_data = context;
project.last_accessed = CurrentTimestamp();
projects_.push_back(project);
}
return SaveProjects();
}
std::optional<LearnedKnowledgeService::ProjectContext>
LearnedKnowledgeService::GetProjectContext(const std::string& project_name) const {
for (const auto& project : projects_) {
if (project.project_name == project_name) {
return project;
}
}
return std::nullopt;
}
std::vector<LearnedKnowledgeService::ProjectContext>
LearnedKnowledgeService::GetAllProjects() const {
return projects_;
}
// === Conversation Memory ===
absl::Status LearnedKnowledgeService::StoreConversationSummary(
const std::string& topic,
const std::string& summary,
const std::vector<std::string>& key_facts) {
if (!initialized_) {
return absl::FailedPreconditionError("Service not initialized");
}
ConversationMemory memory;
memory.id = GenerateRandomID();
memory.topic = topic;
memory.summary = summary;
memory.key_facts = key_facts;
memory.created_at = CurrentTimestamp();
memory.access_count = 1;
memories_.push_back(memory);
// Keep only last 100 memories
if (memories_.size() > 100) {
memories_.erase(memories_.begin());
}
return SaveMemories();
}
std::vector<LearnedKnowledgeService::ConversationMemory>
LearnedKnowledgeService::SearchMemories(const std::string& query) const {
std::vector<ConversationMemory> results;
std::string query_lower = query;
std::transform(query_lower.begin(), query_lower.end(), query_lower.begin(), ::tolower);
for (const auto& memory : memories_) {
std::string topic_lower = memory.topic;
std::string summary_lower = memory.summary;
std::transform(topic_lower.begin(), topic_lower.end(), topic_lower.begin(), ::tolower);
std::transform(summary_lower.begin(), summary_lower.end(), summary_lower.begin(), ::tolower);
if (topic_lower.find(query_lower) != std::string::npos ||
summary_lower.find(query_lower) != std::string::npos) {
results.push_back(memory);
}
}
return results;
}
std::vector<LearnedKnowledgeService::ConversationMemory>
LearnedKnowledgeService::GetRecentMemories(int limit) const {
std::vector<ConversationMemory> recent = memories_;
// Sort by created_at descending
std::sort(recent.begin(), recent.end(),
[](const ConversationMemory& a, const ConversationMemory& b) {
return a.created_at > b.created_at;
});
if (recent.size() > static_cast<size_t>(limit)) {
recent.resize(limit);
}
return recent;
}
// === Import/Export ===
#ifdef YAZE_WITH_JSON
absl::StatusOr<std::string> LearnedKnowledgeService::ExportToJSON() const {
nlohmann::json export_data;
// Export preferences
export_data["preferences"] = preferences_;
// Export patterns
export_data["patterns"] = nlohmann::json::array();
for (const auto& pattern : patterns_) {
nlohmann::json p;
p["type"] = pattern.pattern_type;
p["rom_hash"] = pattern.rom_hash;
p["data"] = pattern.pattern_data;
p["confidence"] = pattern.confidence;
p["learned_at"] = pattern.learned_at;
p["access_count"] = pattern.access_count;
export_data["patterns"].push_back(p);
}
// Export projects
export_data["projects"] = nlohmann::json::array();
for (const auto& project : projects_) {
nlohmann::json p;
p["name"] = project.project_name;
p["rom_hash"] = project.rom_hash;
p["context"] = project.context_data;
p["last_accessed"] = project.last_accessed;
export_data["projects"].push_back(p);
}
// Export memories
export_data["memories"] = nlohmann::json::array();
for (const auto& memory : memories_) {
nlohmann::json m;
m["id"] = memory.id;
m["topic"] = memory.topic;
m["summary"] = memory.summary;
m["key_facts"] = memory.key_facts;
m["created_at"] = memory.created_at;
m["access_count"] = memory.access_count;
export_data["memories"].push_back(m);
}
return export_data.dump(2);
}
absl::Status LearnedKnowledgeService::ImportFromJSON(const std::string& json_data) {
try {
auto data = nlohmann::json::parse(json_data);
// Import preferences
if (data.contains("preferences")) {
for (const auto& [key, value] : data["preferences"].items()) {
preferences_[key] = value.get<std::string>();
}
}
// Import patterns
if (data.contains("patterns")) {
for (const auto& p : data["patterns"]) {
ROMPattern pattern;
pattern.pattern_type = p["type"];
pattern.rom_hash = p["rom_hash"];
pattern.pattern_data = p["data"];
pattern.confidence = p["confidence"];
pattern.learned_at = p["learned_at"];
pattern.access_count = p["access_count"];
patterns_.push_back(pattern);
}
}
// Import projects
if (data.contains("projects")) {
for (const auto& p : data["projects"]) {
ProjectContext project;
project.project_name = p["name"];
project.rom_hash = p["rom_hash"];
project.context_data = p["context"];
project.last_accessed = p["last_accessed"];
projects_.push_back(project);
}
}
// Import memories
if (data.contains("memories")) {
for (const auto& m : data["memories"]) {
ConversationMemory memory;
memory.id = m["id"];
memory.topic = m["topic"];
memory.summary = m["summary"];
memory.key_facts = m["key_facts"].get<std::vector<std::string>>();
memory.created_at = m["created_at"];
memory.access_count = m["access_count"];
memories_.push_back(memory);
}
}
return SaveAll();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("JSON parse error: ", e.what()));
}
}
#else
absl::StatusOr<std::string> LearnedKnowledgeService::ExportToJSON() const {
return absl::UnimplementedError("JSON support not enabled. Build with -DYAZE_WITH_JSON=ON");
}
absl::Status LearnedKnowledgeService::ImportFromJSON(const std::string&) {
return absl::UnimplementedError("JSON support not enabled. Build with -DYAZE_WITH_JSON=ON");
}
#endif
absl::Status LearnedKnowledgeService::ClearAll() {
preferences_.clear();
patterns_.clear();
projects_.clear();
memories_.clear();
return SaveAll();
}
// === Statistics ===
LearnedKnowledgeService::Stats LearnedKnowledgeService::GetStats() const {
Stats stats;
stats.preference_count = preferences_.size();
stats.pattern_count = patterns_.size();
stats.project_count = projects_.size();
stats.memory_count = memories_.size();
// Find earliest learned_at
int64_t earliest = CurrentTimestamp();
for (const auto& pattern : patterns_) {
if (pattern.learned_at < earliest) {
earliest = pattern.learned_at;
}
}
for (const auto& memory : memories_) {
if (memory.created_at < earliest) {
earliest = memory.created_at;
}
}
stats.first_learned_at = earliest;
// Last updated is now
stats.last_updated_at = CurrentTimestamp();
return stats;
}
// === Internal Helpers ===
#ifdef YAZE_WITH_JSON
absl::Status LearnedKnowledgeService::LoadPreferences() {
if (!FileExists(prefs_file_)) {
return absl::OkStatus(); // No file yet, empty preferences
}
try {
std::ifstream file(prefs_file_);
if (!file.is_open()) {
return absl::InternalError("Failed to open preferences file");
}
nlohmann::json data;
file >> data;
for (const auto& [key, value] : data.items()) {
preferences_[key] = value.get<std::string>();
}
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Failed to load preferences: ", e.what()));
}
}
absl::Status LearnedKnowledgeService::SavePreferences() {
try {
nlohmann::json data = preferences_;
std::ofstream file(prefs_file_);
if (!file.is_open()) {
return absl::InternalError("Failed to open preferences file for writing");
}
file << data.dump(2);
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Failed to save preferences: ", e.what()));
}
}
absl::Status LearnedKnowledgeService::LoadPatterns() {
if (!FileExists(patterns_file_)) {
return absl::OkStatus();
}
try {
std::ifstream file(patterns_file_);
nlohmann::json data;
file >> data;
for (const auto& p : data) {
ROMPattern pattern;
pattern.pattern_type = p["type"];
pattern.rom_hash = p["rom_hash"];
pattern.pattern_data = p["data"];
pattern.confidence = p["confidence"];
pattern.learned_at = p["learned_at"];
pattern.access_count = p.value("access_count", 0);
patterns_.push_back(pattern);
}
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Failed to load patterns: ", e.what()));
}
}
absl::Status LearnedKnowledgeService::SavePatterns() {
try {
nlohmann::json data = nlohmann::json::array();
for (const auto& pattern : patterns_) {
nlohmann::json p;
p["type"] = pattern.pattern_type;
p["rom_hash"] = pattern.rom_hash;
p["data"] = pattern.pattern_data;
p["confidence"] = pattern.confidence;
p["learned_at"] = pattern.learned_at;
p["access_count"] = pattern.access_count;
data.push_back(p);
}
std::ofstream file(patterns_file_);
file << data.dump(2);
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Failed to save patterns: ", e.what()));
}
}
absl::Status LearnedKnowledgeService::LoadProjects() {
if (!FileExists(projects_file_)) {
return absl::OkStatus();
}
try {
std::ifstream file(projects_file_);
nlohmann::json data;
file >> data;
for (const auto& p : data) {
ProjectContext project;
project.project_name = p["name"];
project.rom_hash = p["rom_hash"];
project.context_data = p["context"];
project.last_accessed = p["last_accessed"];
projects_.push_back(project);
}
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Failed to load projects: ", e.what()));
}
}
absl::Status LearnedKnowledgeService::SaveProjects() {
try {
nlohmann::json data = nlohmann::json::array();
for (const auto& project : projects_) {
nlohmann::json p;
p["name"] = project.project_name;
p["rom_hash"] = project.rom_hash;
p["context"] = project.context_data;
p["last_accessed"] = project.last_accessed;
data.push_back(p);
}
std::ofstream file(projects_file_);
file << data.dump(2);
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Failed to save projects: ", e.what()));
}
}
absl::Status LearnedKnowledgeService::SaveMemories() {
try {
nlohmann::json data = nlohmann::json::array();
for (const auto& memory : memories_) {
nlohmann::json m;
m["id"] = memory.id;
m["topic"] = memory.topic;
m["summary"] = memory.summary;
m["key_facts"] = memory.key_facts;
m["created_at"] = memory.created_at;
m["access_count"] = memory.access_count;
data.push_back(m);
}
std::ofstream file(memories_file_);
file << data.dump(2);
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Failed to save memories: ", e.what()));
}
}
absl::Status LearnedKnowledgeService::LoadMemories() {
if (!FileExists(memories_file_)) {
return absl::OkStatus();
}
try {
std::ifstream file(memories_file_);
nlohmann::json data;
file >> data;
for (const auto& m : data) {
ConversationMemory memory;
memory.id = m["id"];
memory.topic = m["topic"];
memory.summary = m["summary"];
memory.key_facts = m["key_facts"].get<std::vector<std::string>>();
memory.created_at = m["created_at"];
memory.access_count = m.value("access_count", 0);
memories_.push_back(memory);
}
return absl::OkStatus();
} catch (const std::exception& e) {
return absl::InternalError(absl::StrCat("Failed to load memories: ", e.what()));
}
}
#else
// Stub implementations when JSON is not available
absl::Status LearnedKnowledgeService::LoadPreferences() { return absl::OkStatus(); }
absl::Status LearnedKnowledgeService::SavePreferences() { return absl::UnimplementedError("JSON support required"); }
absl::Status LearnedKnowledgeService::LoadPatterns() { return absl::OkStatus(); }
absl::Status LearnedKnowledgeService::SavePatterns() { return absl::UnimplementedError("JSON support required"); }
absl::Status LearnedKnowledgeService::LoadProjects() { return absl::OkStatus(); }
absl::Status LearnedKnowledgeService::SaveProjects() { return absl::UnimplementedError("JSON support required"); }
absl::Status LearnedKnowledgeService::LoadMemories() { return absl::OkStatus(); }
absl::Status LearnedKnowledgeService::SaveMemories() { return absl::UnimplementedError("JSON support required"); }
#endif
std::string LearnedKnowledgeService::GenerateID() const {
return GenerateRandomID();
}
} // namespace agent
} // namespace cli
} // namespace yaze

View File

@@ -0,0 +1,225 @@
#ifndef YAZE_CLI_SERVICE_AGENT_LEARNED_KNOWLEDGE_SERVICE_H_
#define YAZE_CLI_SERVICE_AGENT_LEARNED_KNOWLEDGE_SERVICE_H_
#include <filesystem>
#include <map>
#include <optional>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#ifdef YAZE_WITH_JSON
#include "nlohmann/json.hpp"
#endif
namespace yaze {
namespace cli {
namespace agent {
/**
* @class LearnedKnowledgeService
* @brief Manages persistent learned information across agent sessions
*
* Stores:
* - User preferences (default palettes, favorite tools, etc.)
* - ROM patterns (frequently accessed rooms, sprite placements, etc.)
* - Project context (ROM-specific notes, goals, custom names)
* - Conversation memory (summaries of past discussions)
*/
class LearnedKnowledgeService {
public:
LearnedKnowledgeService();
explicit LearnedKnowledgeService(const std::filesystem::path& data_dir);
// Initialize the service and load existing data
absl::Status Initialize();
// Save all data to disk
absl::Status SaveAll();
// === Preference Management ===
/**
* Set a user preference
* @param key Preference key (e.g., "default_palette", "preferred_tool")
* @param value Preference value
*/
absl::Status SetPreference(const std::string& key, const std::string& value);
/**
* Get a user preference
* @param key Preference key
* @return Value if exists, nullopt otherwise
*/
std::optional<std::string> GetPreference(const std::string& key) const;
/**
* List all preferences
*/
std::map<std::string, std::string> GetAllPreferences() const;
/**
* Remove a preference
*/
absl::Status RemovePreference(const std::string& key);
// === ROM Pattern Learning ===
struct ROMPattern {
std::string pattern_type; // e.g., "sprite_location", "tile_usage"
std::string rom_hash;
std::string pattern_data; // JSON encoded
float confidence = 1.0f; // 0.0 to 1.0
int64_t learned_at = 0;
int access_count = 0;
};
/**
* Learn a pattern from the current ROM
* @param type Pattern type (e.g., "sprite_distribution", "room_access_frequency")
* @param rom_hash SHA256 hash of the ROM
* @param data Pattern-specific data (JSON)
*/
absl::Status LearnPattern(const std::string& type,
const std::string& rom_hash,
const std::string& data,
float confidence = 1.0f);
/**
* Query patterns for a specific ROM
* @param type Pattern type to query
* @param rom_hash ROM hash to filter by (empty = all ROMs)
* @return Vector of matching patterns
*/
std::vector<ROMPattern> QueryPatterns(const std::string& type,
const std::string& rom_hash = "") const;
/**
* Update pattern confidence/access count
*/
absl::Status UpdatePatternConfidence(const std::string& type,
const std::string& rom_hash,
float new_confidence);
// === Project Context ===
struct ProjectContext {
std::string project_name;
std::string rom_hash;
std::string context_data; // JSON encoded: description, goals, custom labels
int64_t last_accessed = 0;
};
/**
* Save context for a project/ROM
*/
absl::Status SaveProjectContext(const std::string& project_name,
const std::string& rom_hash,
const std::string& context);
/**
* Get project context
*/
std::optional<ProjectContext> GetProjectContext(const std::string& project_name) const;
/**
* List all projects
*/
std::vector<ProjectContext> GetAllProjects() const;
// === Conversation Memory ===
struct ConversationMemory {
std::string id;
std::string topic;
std::string summary;
std::vector<std::string> key_facts;
int64_t created_at = 0;
int access_count = 0;
};
/**
* Store a conversation summary
* @param topic Topic/theme of the conversation
* @param summary Brief summary
* @param key_facts Important facts extracted
*/
absl::Status StoreConversationSummary(const std::string& topic,
const std::string& summary,
const std::vector<std::string>& key_facts);
/**
* Search conversation memories by topic/keyword
*/
std::vector<ConversationMemory> SearchMemories(const std::string& query) const;
/**
* Get most recent conversation memories
*/
std::vector<ConversationMemory> GetRecentMemories(int limit = 10) const;
// === Import/Export ===
/**
* Export all learned data to JSON
*/
absl::StatusOr<std::string> ExportToJSON() const;
/**
* Import learned data from JSON
*/
absl::Status ImportFromJSON(const std::string& json_data);
/**
* Clear all learned data
*/
absl::Status ClearAll();
// === Statistics ===
struct Stats {
int preference_count = 0;
int pattern_count = 0;
int project_count = 0;
int memory_count = 0;
int64_t first_learned_at = 0;
int64_t last_updated_at = 0;
};
Stats GetStats() const;
private:
std::filesystem::path data_dir_;
std::filesystem::path prefs_file_;
std::filesystem::path patterns_file_;
std::filesystem::path projects_file_;
std::filesystem::path memories_file_;
std::map<std::string, std::string> preferences_;
std::vector<ROMPattern> patterns_;
std::vector<ProjectContext> projects_;
std::vector<ConversationMemory> memories_;
bool initialized_ = false;
// Internal helpers
absl::Status LoadPreferences();
absl::Status LoadPatterns();
absl::Status LoadProjects();
absl::Status LoadMemories();
absl::Status SavePreferences();
absl::Status SavePatterns();
absl::Status SaveProjects();
absl:Status SaveMemories();
std::string GenerateID() const;
};
} // namespace agent
} // namespace cli
} // namespace yaze
#endif // YAZE_CLI_SERVICE_AGENT_LEARNED_KNOWLEDGE_SERVICE_H_