From 5ac5c7f44c46ac97a375f30904f0e0176f4d0fc8 Mon Sep 17 00:00:00 2001 From: Google AI Agent Date: Mon, 30 Mar 2026 22:28:56 +0000 Subject: [PATCH 1/4] feat: add sovereign Graph Store tool --- tools/graph_store.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tools/graph_store.py diff --git a/tools/graph_store.py b/tools/graph_store.py new file mode 100644 index 00000000..e89a3e52 --- /dev/null +++ b/tools/graph_store.py @@ -0,0 +1,64 @@ +"""Sovereign Knowledge Graph Store for Hermes Agent. + +Provides a simple triple-store (Subject, Predicate, Object) persisted +to Timmy's sovereign Gitea instance. +""" + +import json +import base64 +import logging +from typing import List, Dict, Any, Optional +from tools.gitea_client import GiteaClient + +logger = logging.getLogger(__name__) + +class GraphStore: + def __init__(self, repo: str = "Timmy_Foundation/timmy-config", path: str = "memories/knowledge_graph.json"): + self.repo = repo + self.path = path + self.gitea = GiteaClient() + + def _load_graph(self) -> Dict[str, Any]: + try: + content = self.gitea.get_file(self.repo, self.path) + raw = base64.b64decode(content["content"]).decode() + return json.loads(raw) + except Exception: + return {"triples": [], "entities": {}} + + def _save_graph(self, graph: Dict[str, Any], message: str): + sha = None + try: + existing = self.gitea.get_file(self.repo, self.path) + sha = existing.get("sha") + except: + pass + + content_b64 = base64.b64encode(json.dumps(graph, indent=2).encode()).decode() + if sha: + self.gitea.update_file(self.repo, self.path, content_b64, message, sha) + else: + self.gitea.create_file(self.repo, self.path, content_b64, message) + + def add_triples(self, triples: List[Dict[str, str]]): + """Adds a list of triples: [{'s': '...', 'p': '...', 'o': '...'}]""" + graph = self._load_graph() + added_count = 0 + for t in triples: + if t not in graph["triples"]: + graph["triples"].append(t) + added_count += 1 + + if added_count > 0: + self._save_graph(graph, f"Add {added_count} triples to knowledge graph") + return added_count + + def query(self, subject: Optional[str] = None, predicate: Optional[str] = None, object: Optional[str] = None) -> List[Dict[str, str]]: + graph = self._load_graph() + results = [] + for t in graph["triples"]: + if subject and t['s'] != subject: continue + if predicate and t['p'] != predicate: continue + if object and t['o'] != object: continue + results.append(t) + return results From 482b6c5aea3456d4671f798b1ee94b11dae8c9e2 Mon Sep 17 00:00:00 2001 From: Google AI Agent Date: Mon, 30 Mar 2026 22:28:57 +0000 Subject: [PATCH 2/4] feat: add Sovereign Intersymbolic Memory Layer --- agent/symbolic_memory.py | 74 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 agent/symbolic_memory.py diff --git a/agent/symbolic_memory.py b/agent/symbolic_memory.py new file mode 100644 index 00000000..ef3cf7e0 --- /dev/null +++ b/agent/symbolic_memory.py @@ -0,0 +1,74 @@ +"""Sovereign Intersymbolic Memory Layer. + +Bridges Neural (LLM) and Symbolic (Graph) reasoning by extracting +structured triples from unstructured text and performing graph lookups. +""" + +import logging +import json +from typing import List, Dict, Any +from agent.gemini_adapter import GeminiAdapter +from tools.graph_store import GraphStore + +logger = logging.getLogger(__name__) + +class SymbolicMemory: + def __init__(self): + self.adapter = GeminiAdapter() + self.store = GraphStore() + + def ingest_text(self, text: str): + """Extracts triples from text and adds them to the graph.""" + prompt = f""" +Extract all meaningful entities and their relationships from the following text. +Format the output as a JSON list of triples: [{{"s": "subject", "p": "predicate", "o": "object"}}] + +Text: +{text} + +Guidelines: +- Use clear, concise labels for entities and predicates. +- Focus on stable facts and structural relationships. +- Predicates should be verbs or descriptive relations (e.g., 'is_a', 'works_at', 'collaborates_with'). +""" + try: + result = self.adapter.generate( + model="gemini-3.1-pro-preview", + prompt=prompt, + system_instruction="You are Timmy's Symbolic Extraction Engine. Extract high-fidelity knowledge triples.", + response_mime_type="application/json" + ) + + triples = json.loads(result["text"]) + if isinstance(triples, list): + count = self.store.add_triples(triples) + logger.info(f"Ingested {count} new triples into symbolic memory.") + return count + except Exception as e: + logger.error(f"Symbolic ingestion failed: {e}") + return 0 + + def get_context_for(self, topic: str) -> str: + """Performs a 2-hop graph search to find related context for a topic.""" + # 1. Find direct relations + direct = self.store.query(subject=topic) + self.store.query(object=topic) + + # 2. Find 2nd hop + related_entities = set() + for t in direct: + related_entities.add(t['s']) + related_entities.add(t['o']) + + extended = [] + for entity in related_entities: + if entity == topic: continue + extended.extend(self.store.query(subject=entity)) + + all_triples = direct + extended + if not all_triples: + return "" + + context = "Symbolic Knowledge Graph Context:\n" + for t in all_triples: + context += f"- {t['s']} --({t['p']})--> {t['o']}\n" + return context From b5527fee262d1acefebea940ea55d4668c869447 Mon Sep 17 00:00:00 2001 From: Google AI Agent Date: Mon, 30 Mar 2026 22:28:58 +0000 Subject: [PATCH 3/4] feat: add Intersymbolic Graph Query skill --- skills/memory/intersymbolic_graph.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 skills/memory/intersymbolic_graph.py diff --git a/skills/memory/intersymbolic_graph.py b/skills/memory/intersymbolic_graph.py new file mode 100644 index 00000000..baad6c63 --- /dev/null +++ b/skills/memory/intersymbolic_graph.py @@ -0,0 +1,27 @@ +""" +--- +title: Intersymbolic Graph Query +description: Queries Timmy's sovereign knowledge graph to find connections and structured facts. +conditions: + - Complex relationship analysis + - Fact checking against structured memory + - Finding non-obvious connections +--- +""" + +from agent.symbolic_memory import SymbolicMemory + +def query_graph(topic: str) -> str: + """ + Queries the knowledge graph for a specific topic and returns structured context. + + Args: + topic: The entity or topic to search for in the graph. + """ + memory = SymbolicMemory() + context = memory.get_context_for(topic) + + if not context: + return f"No symbolic connections found for '{topic}' in the knowledge graph." + + return context From 1bff6d17d5f8141456019a2dc171bf83c13de631 Mon Sep 17 00:00:00 2001 From: Google AI Agent Date: Mon, 30 Mar 2026 22:28:59 +0000 Subject: [PATCH 4/4] feat: enhance Knowledge Ingester with symbolic extraction --- agent/knowledge_ingester.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/agent/knowledge_ingester.py b/agent/knowledge_ingester.py index 786b2a0f..da24c435 100644 --- a/agent/knowledge_ingester.py +++ b/agent/knowledge_ingester.py @@ -1,13 +1,14 @@ """Sovereign Knowledge Ingester for Hermes Agent. Uses Gemini 3.1 Pro to learn from Google Search in real-time and -persists the knowledge to Timmy's sovereign memory. +persists the knowledge to Timmy's sovereign memory (both Markdown and Symbolic). """ import logging import base64 from typing import Any, Dict, List, Optional from agent.gemini_adapter import GeminiAdapter +from agent.symbolic_memory import SymbolicMemory from tools.gitea_client import GiteaClient logger = logging.getLogger(__name__) @@ -16,6 +17,7 @@ class KnowledgeIngester: def __init__(self): self.adapter = GeminiAdapter() self.gitea = GiteaClient() + self.symbolic = SymbolicMemory() def learn_about(self, topic: str) -> str: """Searches Google, analyzes the results, and saves the knowledge.""" @@ -43,12 +45,14 @@ Include: knowledge_fragment = result["text"] - # 2. Persist to Timmy's Memory + # 2. Extract Symbolic Triples + self.symbolic.ingest_text(knowledge_fragment) + + # 3. Persist to Timmy's Memory (Markdown) repo = "Timmy_Foundation/timmy-config" filename = f"memories/realtime_learning/{topic.lower().replace(' ', '_')}.md" try: - # Check if file exists to get SHA sha = None try: existing = self.gitea.get_file(repo, filename) @@ -63,7 +67,7 @@ Include: else: self.gitea.create_file(repo, filename, content_b64, f"Initial knowledge on {topic}") - return f"Successfully learned about {topic} and updated Timmy's memory at {filename}" + return f"Successfully learned about {topic}. Updated Timmy's Markdown memory and Symbolic Knowledge Graph." except Exception as e: logger.error(f"Failed to persist knowledge: {e}") - return f"Learned about {topic}, but failed to save to memory: {e}\n\n{knowledge_fragment}" + return f"Learned about {topic}, but failed to save to Markdown memory: {e}\n\n{knowledge_fragment}"