@geravant/sinain 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -0
- package/index.ts +2096 -0
- package/install.js +155 -0
- package/openclaw.plugin.json +59 -0
- package/package.json +21 -0
- package/sinain-memory/common.py +403 -0
- package/sinain-memory/demo_knowledge_transfer.sh +85 -0
- package/sinain-memory/embedder.py +268 -0
- package/sinain-memory/eval/__init__.py +0 -0
- package/sinain-memory/eval/assertions.py +288 -0
- package/sinain-memory/eval/judges/__init__.py +0 -0
- package/sinain-memory/eval/judges/base_judge.py +61 -0
- package/sinain-memory/eval/judges/curation_judge.py +46 -0
- package/sinain-memory/eval/judges/insight_judge.py +48 -0
- package/sinain-memory/eval/judges/mining_judge.py +42 -0
- package/sinain-memory/eval/judges/signal_judge.py +45 -0
- package/sinain-memory/eval/schemas.py +247 -0
- package/sinain-memory/eval_delta.py +109 -0
- package/sinain-memory/eval_reporter.py +642 -0
- package/sinain-memory/feedback_analyzer.py +221 -0
- package/sinain-memory/git_backup.sh +19 -0
- package/sinain-memory/insight_synthesizer.py +181 -0
- package/sinain-memory/memory/2026-03-01.md +11 -0
- package/sinain-memory/memory/playbook-archive/sinain-playbook-2026-03-01-1418.md +15 -0
- package/sinain-memory/memory/playbook-logs/2026-03-01.jsonl +1 -0
- package/sinain-memory/memory/sinain-playbook.md +21 -0
- package/sinain-memory/memory-config.json +39 -0
- package/sinain-memory/memory_miner.py +183 -0
- package/sinain-memory/module_manager.py +695 -0
- package/sinain-memory/playbook_curator.py +225 -0
- package/sinain-memory/requirements.txt +3 -0
- package/sinain-memory/signal_analyzer.py +141 -0
- package/sinain-memory/test_local.py +402 -0
- package/sinain-memory/tests/__init__.py +0 -0
- package/sinain-memory/tests/conftest.py +189 -0
- package/sinain-memory/tests/test_curator_helpers.py +94 -0
- package/sinain-memory/tests/test_embedder.py +210 -0
- package/sinain-memory/tests/test_extract_json.py +124 -0
- package/sinain-memory/tests/test_feedback_computation.py +121 -0
- package/sinain-memory/tests/test_miner_helpers.py +71 -0
- package/sinain-memory/tests/test_module_management.py +458 -0
- package/sinain-memory/tests/test_parsers.py +96 -0
- package/sinain-memory/tests/test_tick_evaluator.py +430 -0
- package/sinain-memory/tests/test_triple_extractor.py +255 -0
- package/sinain-memory/tests/test_triple_ingest.py +191 -0
- package/sinain-memory/tests/test_triple_migrate.py +138 -0
- package/sinain-memory/tests/test_triplestore.py +248 -0
- package/sinain-memory/tick_evaluator.py +392 -0
- package/sinain-memory/triple_extractor.py +402 -0
- package/sinain-memory/triple_ingest.py +290 -0
- package/sinain-memory/triple_migrate.py +275 -0
- package/sinain-memory/triple_query.py +184 -0
- package/sinain-memory/triplestore.py +498 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Phase 3 Step 1 (idle): Memory Miner — deep-mine daily memory files.
|
|
3
|
+
|
|
4
|
+
Reads mining index from playbook to find unmined files, reads 2 daily memory
|
|
5
|
+
files + devmatrix-summary.md, uses LLM to find patterns and cross-references.
|
|
6
|
+
Updates the mining index in the playbook.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 memory_miner.py --memory-dir memory/
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
from datetime import datetime, timedelta, timezone
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from common import (
|
|
20
|
+
LLMError,
|
|
21
|
+
call_llm,
|
|
22
|
+
extract_json,
|
|
23
|
+
list_daily_memory_files,
|
|
24
|
+
output_json,
|
|
25
|
+
parse_mining_index,
|
|
26
|
+
read_effective_playbook,
|
|
27
|
+
read_file_safe,
|
|
28
|
+
read_playbook,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
SYSTEM_PROMPT = """\
|
|
32
|
+
You are a memory mining agent for a personal AI assistant (sinain).
|
|
33
|
+
Your job: read daily memory files and extract patterns, preferences, and insights
|
|
34
|
+
that should be added to the evolving playbook.
|
|
35
|
+
|
|
36
|
+
You receive daily memory files (markdown with session notes, decisions, research)
|
|
37
|
+
and the current playbook. Cross-reference to find:
|
|
38
|
+
|
|
39
|
+
1. Patterns that appear across multiple days but aren't in the playbook
|
|
40
|
+
2. User preferences (tools, workflows, topics) that are consistent
|
|
41
|
+
3. Multi-day trends: recurring errors, evolving interests, productivity rhythms
|
|
42
|
+
4. Contradictions: daily notes that conflict with playbook entries
|
|
43
|
+
5. Architectural decisions or technical insights worth preserving
|
|
44
|
+
|
|
45
|
+
Respond with ONLY a JSON object:
|
|
46
|
+
{
|
|
47
|
+
"findings": "2-3 sentence summary of what was discovered",
|
|
48
|
+
"newPatterns": ["pattern description", ...],
|
|
49
|
+
"contradictions": ["playbook entry X contradicts observation Y", ...],
|
|
50
|
+
"preferences": ["user preference observed", ...]
|
|
51
|
+
}"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_unmined_files(memory_dir: str, mined_dates: list[str]) -> list[str]:
|
|
55
|
+
"""Find daily memory files not yet mined (not in index)."""
|
|
56
|
+
all_files = list_daily_memory_files(memory_dir)
|
|
57
|
+
unmined = []
|
|
58
|
+
for f in all_files:
|
|
59
|
+
# Extract date from filename (YYYY-MM-DD.md)
|
|
60
|
+
stem = Path(f).stem # "2026-02-17"
|
|
61
|
+
if stem not in mined_dates:
|
|
62
|
+
unmined.append(f)
|
|
63
|
+
return unmined
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def update_mining_index(memory_dir: str, playbook: str, new_dates: list[str]) -> None:
|
|
67
|
+
"""Update mining-index comment in playbook, removing dates older than 7 days."""
|
|
68
|
+
current_index = parse_mining_index(playbook)
|
|
69
|
+
cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).strftime("%Y-%m-%d")
|
|
70
|
+
|
|
71
|
+
# Merge and filter
|
|
72
|
+
all_dates = set(current_index + new_dates)
|
|
73
|
+
valid_dates = sorted([d for d in all_dates if d >= cutoff], reverse=True)
|
|
74
|
+
new_index_str = ",".join(valid_dates)
|
|
75
|
+
new_comment = f"<!-- mining-index: {new_index_str} -->"
|
|
76
|
+
|
|
77
|
+
# Replace or insert
|
|
78
|
+
playbook_path = Path(memory_dir) / "sinain-playbook.md"
|
|
79
|
+
if not playbook_path.exists():
|
|
80
|
+
playbook_path.write_text(new_comment + "\n", encoding="utf-8")
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
text = playbook_path.read_text(encoding="utf-8")
|
|
84
|
+
if re.search(r"<!--\s*mining-index:", text):
|
|
85
|
+
text = re.sub(r"<!--\s*mining-index:\s*[^>]*-->", new_comment, text)
|
|
86
|
+
else:
|
|
87
|
+
text = new_comment + "\n" + text
|
|
88
|
+
|
|
89
|
+
playbook_path.write_text(text, encoding="utf-8")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main():
|
|
93
|
+
parser = argparse.ArgumentParser(description="Phase 3: Memory mining (idle)")
|
|
94
|
+
parser.add_argument("--memory-dir", required=True, help="Path to memory/ directory")
|
|
95
|
+
args = parser.parse_args()
|
|
96
|
+
|
|
97
|
+
raw_playbook = read_playbook(args.memory_dir)
|
|
98
|
+
playbook = read_effective_playbook(args.memory_dir)
|
|
99
|
+
mined_dates = parse_mining_index(raw_playbook)
|
|
100
|
+
unmined = get_unmined_files(args.memory_dir, mined_dates)
|
|
101
|
+
|
|
102
|
+
if not unmined:
|
|
103
|
+
output_json({
|
|
104
|
+
"findings": "All daily memory files have been mined",
|
|
105
|
+
"newPatterns": [],
|
|
106
|
+
"minedSources": [],
|
|
107
|
+
})
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
# Pick up to 3 unmined files
|
|
111
|
+
to_mine = unmined[:3]
|
|
112
|
+
mined_contents = {}
|
|
113
|
+
for f in to_mine:
|
|
114
|
+
content = read_file_safe(f)
|
|
115
|
+
if content:
|
|
116
|
+
mined_contents[Path(f).name] = content
|
|
117
|
+
|
|
118
|
+
if not mined_contents:
|
|
119
|
+
output_json({
|
|
120
|
+
"findings": "Selected daily files were empty",
|
|
121
|
+
"newPatterns": [],
|
|
122
|
+
"minedSources": [Path(f).name for f in to_mine],
|
|
123
|
+
})
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
# Also read devmatrix-summary.md for broader context
|
|
127
|
+
devmatrix = read_file_safe(str(Path(args.memory_dir) / "devmatrix-summary.md"))
|
|
128
|
+
|
|
129
|
+
# Build LLM prompt
|
|
130
|
+
parts = [f"## Current Playbook\n{playbook}"]
|
|
131
|
+
for name, content in mined_contents.items():
|
|
132
|
+
# Truncate very large files
|
|
133
|
+
if len(content) > 6000:
|
|
134
|
+
content = content[:6000] + "\n... [truncated]"
|
|
135
|
+
parts.append(f"## Daily Memory: {name}\n{content}")
|
|
136
|
+
if devmatrix:
|
|
137
|
+
if len(devmatrix) > 3000:
|
|
138
|
+
devmatrix = devmatrix[:3000] + "\n... [truncated]"
|
|
139
|
+
parts.append(f"## DevMatrix Summary\n{devmatrix}")
|
|
140
|
+
|
|
141
|
+
# Inject graph context if triple store available
|
|
142
|
+
try:
|
|
143
|
+
from triple_query import get_related_concepts
|
|
144
|
+
keywords = [Path(f).stem for f in to_mine]
|
|
145
|
+
graph_ctx = get_related_concepts(args.memory_dir, keywords)
|
|
146
|
+
if graph_ctx:
|
|
147
|
+
parts.append(f"## Knowledge Graph Context\n{graph_ctx}")
|
|
148
|
+
except ImportError:
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
user_prompt = "\n\n".join(parts)
|
|
152
|
+
|
|
153
|
+
llm_ok = False
|
|
154
|
+
try:
|
|
155
|
+
raw = call_llm(SYSTEM_PROMPT, user_prompt, script="memory_miner", json_mode=True)
|
|
156
|
+
result = extract_json(raw)
|
|
157
|
+
llm_ok = True
|
|
158
|
+
except (ValueError, LLMError) as e:
|
|
159
|
+
print(f"[warn] {e}", file=sys.stderr)
|
|
160
|
+
result = {
|
|
161
|
+
"findings": "Mining completed but LLM response was not parseable",
|
|
162
|
+
"newPatterns": [],
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# Only mark files as mined on successful parse — failed files will be retried
|
|
166
|
+
new_dates = [Path(f).stem for f in to_mine]
|
|
167
|
+
if llm_ok:
|
|
168
|
+
update_mining_index(args.memory_dir, raw_playbook, new_dates)
|
|
169
|
+
print(f"[info] Updated mining index with {new_dates}", file=sys.stderr)
|
|
170
|
+
else:
|
|
171
|
+
print(f"[info] Skipped mining index update (LLM failed) — {new_dates} will be retried", file=sys.stderr)
|
|
172
|
+
|
|
173
|
+
output_json({
|
|
174
|
+
"findings": result.get("findings", ""),
|
|
175
|
+
"newPatterns": result.get("newPatterns", []),
|
|
176
|
+
"contradictions": result.get("contradictions", []),
|
|
177
|
+
"preferences": result.get("preferences", []),
|
|
178
|
+
"minedSources": [Path(f).name for f in to_mine],
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == "__main__":
|
|
183
|
+
main()
|