Skill: elmoorish/temporal-kg-synthesizer (version 0.1.0)

--- name: "Temporal Knowledge Graph Synthesizer" description: "Actively parses daily session logs and unstructured memory files to extract entities, temporal data, and relationships into a structured Knowledge Graph." version: 1.0.0 author: "OpenClaw Community" metadata: openclaw: install: - "uv pip install spacy networkx pyyaml" - "uv run python -m spacy download en_core_web_sm" requires: bins: ["python"] --- # Temporal Knowledge Graph Synthesizer You are the Ontological Metabolism engine for this OpenClaw workspace. Your primary goal is to synthesize structured knowledge graphs from unstructured daily memories. When you are invoked, a background script will automatically parse recent memories and update the entity graphs. ## Computed Graph Status: !python3 ${OPENCLAW_SKILL_DIR:-~/.openclaw/skills/temporal-kg-synthesizer}/scripts/build_kg.py Read the summary above. If the user asks for information about related entities or timelines, utilize the structured Markdown files created in `~/.openclaw/workspace/memory/entities/` to answer.

path: cron_setup.sh
content: #!/usr/bin/env bash

# This script registers the background cron job for the Temporal KG Synthesizer
# It will run at 3 AM daily to parse logs from the previous day without interrupting active sessions.

openclaw cron add \
  --name "Temporal KG Synthesis" \
  --cron "0 3 * * *" \
  --session isolated \
  --message "Synthesize the temporal knowledge graph. Parse our recent daily logs and construct relationship mappings."

echo "Cron job 'Temporal KG Synthesis' successfully registered."


path: scripts/build_kg.py
content: #!/usr/bin/env python3
import os
import re
import json
import glob
from datetime import datetime
import yaml

# If spacy is not installed (e.g., during testing), gracefully degrade to regex fallback
try:
    import spacy
    nlp = spacy.load("en_core_web_sm")
except ImportError:
    nlp = None

WORKSPACE_MEM_DIR = os.path.expanduser("~/.openclaw/workspace/memory")
ENTITIES_DIR = os.path.join(WORKSPACE_MEM_DIR, "entities")

def ensure_dir(path):
    if not os.path.exists(path):
        os.makedirs(path, exist_ok=True)

def parse_daily_logs():
    files = glob.glob(os.path.join(WORKSPACE_MEM_DIR, "????-??-??.md"))
    content = ""
    for f in files:
        with open(f, 'r', encoding='utf-8') as file:
            content += f"\n--- Source: {os.path.basename(f)} ---\n"
            content += file.read()
    return content

def extract_entities(text):
    entities = {}
    if nlp:
        doc = nlp(text)
        for ent in doc.ents:
            if ent.label_ in ["ORG", "PERSON", "PRODUCT", "LOC"]:
                name = ent.text.strip().lower().replace(' ', '_')
                if name not in entities:
                    entities[name] = {"type": ent.label_, "mentions": 1, "relationships": []}
                else:
                    entities[name]["mentions"] += 1
    else:
        # Fallback simplistic extraction for demonstration
        words = re.findall(r'\b[A-Z][A-Za-z-]+\b', text)
        for w in words:
            if len(w) > 3:
                name = w.lower().strip()
                if name not in entities:
                    entities[name] = {"type": "UNKNOWN", "mentions": 1, "relationships": []}
                else:
                    entities[name]["mentions"] += 1
    return entities

def write_entity_markdown(entities):
    ensure_dir(ENTITIES_DIR)
    created_files = 0
    for name, data in entities.items():
        if data["mentions"] > 1: # Only track entities mentioned multiple times
            filepath = os.path.join(ENTITIES_DIR, f"{name}.md")
            frontmatter = {
                "entity": name,
                "type": data["type"],
                "mentions": data["mentions"],
                "last_updated": datetime.now().isoformat()
            }
            
            # Merge if exists
            existing_relationships = []
            if os.path.exists(filepath):
                with open(filepath, 'r', encoding='utf-8') as f:
                    content = f.read()
                    if content.startswith('---'):
                        try:
                            # Basic split to read frontmatter (not robust for production, but sufficient for here)
                            parts = content.split('---', 2)
                            if len(parts) >= 3:
                                old_fm = yaml.safe_load(parts[1])
                                frontmatter["mentions"] += old_fm.get("mentions", 0)
                        except Exception:
                            pass

            with open(filepath, 'w', encoding='utf-8') as f:
                f.write("---\n")
                f.write(yaml.dump(frontmatter))
                f.write("---\n\n")
                f.write(f"# {name.title()}\n\n")
                f.write("This file is automatically generated by the Temporal KG Synthesizer.\n")
            created_files += 1
            
    return created_files

if __name__ == "__main__":
    ensure_dir(WORKSPACE_MEM_DIR)
    text = parse_daily_logs()
    if text.strip():
        count = write_entity_markdown(extract_entities(text))
        print(f"**Knowledge Graph Updated**: Successfully parsed memory logs and synthesized {count} distinct entities into `{ENTITIES_DIR}`.")
    else:
        print("**Knowledge Graph Synthesis**: No daily logs found. Waiting for memory entries.")


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)