@reconcrap/people-network-memory 0.1.0
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 +476 -0
- package/docs/mcp_tools.md +138 -0
- package/harness_adapters/openclaw/mcp.managed.unix.template.json +25 -0
- package/harness_adapters/openclaw/mcp.managed.windows.template.json +26 -0
- package/harness_adapters/openclaw/mcp.template.json +14 -0
- package/harness_adapters/openclaw/ppl/SKILL.md +114 -0
- package/package.json +30 -0
- package/pyproject.toml +26 -0
- package/scripts/install_windows.ps1 +92 -0
- package/scripts/npm/people-memory.js +276 -0
- package/scripts/people_memory_bootstrap.py +247 -0
- package/scripts/run_graphiti_live_from_liepin.ps1 +87 -0
- package/scripts/run_tests_with_artifacts.ps1 +307 -0
- package/src/people_network_memory/__init__.py +6 -0
- package/src/people_network_memory/application/__init__.py +16 -0
- package/src/people_network_memory/application/normalization.py +1441 -0
- package/src/people_network_memory/application/services.py +921 -0
- package/src/people_network_memory/cli.py +1212 -0
- package/src/people_network_memory/config.py +268 -0
- package/src/people_network_memory/domain/__init__.py +55 -0
- package/src/people_network_memory/domain/identity.py +77 -0
- package/src/people_network_memory/domain/models.py +355 -0
- package/src/people_network_memory/fixtures/__init__.py +6 -0
- package/src/people_network_memory/fixtures/eval.py +398 -0
- package/src/people_network_memory/fixtures/extractor_eval.py +364 -0
- package/src/people_network_memory/fixtures/generator.py +290 -0
- package/src/people_network_memory/fixtures/report.py +252 -0
- package/src/people_network_memory/graphiti_adapter/__init__.py +9 -0
- package/src/people_network_memory/graphiti_adapter/episode_formatter.py +70 -0
- package/src/people_network_memory/graphiti_adapter/graphiti_store.py +655 -0
- package/src/people_network_memory/graphiti_adapter/indexer.py +194 -0
- package/src/people_network_memory/graphiti_adapter/ontology.py +68 -0
- package/src/people_network_memory/harness_adapters/__init__.py +2 -0
- package/src/people_network_memory/harness_adapters/openclaw/__init__.py +9 -0
- package/src/people_network_memory/harness_adapters/openclaw/installer.py +577 -0
- package/src/people_network_memory/harness_adapters/openclaw/integration_eval.py +508 -0
- package/src/people_network_memory/harness_adapters/openclaw/smoke.py +292 -0
- package/src/people_network_memory/infrastructure/__init__.py +2 -0
- package/src/people_network_memory/infrastructure/archive_backup.py +171 -0
- package/src/people_network_memory/infrastructure/diagnostics.py +171 -0
- package/src/people_network_memory/infrastructure/embeddings.py +155 -0
- package/src/people_network_memory/infrastructure/file_store.py +129 -0
- package/src/people_network_memory/infrastructure/graphiti_promotion.py +212 -0
- package/src/people_network_memory/infrastructure/id_generator.py +40 -0
- package/src/people_network_memory/infrastructure/in_memory_store.py +1008 -0
- package/src/people_network_memory/infrastructure/llm_extractor.py +476 -0
- package/src/people_network_memory/infrastructure/llm_identity_advisor.py +200 -0
- package/src/people_network_memory/infrastructure/llm_judge.py +162 -0
- package/src/people_network_memory/infrastructure/redaction.py +21 -0
- package/src/people_network_memory/infrastructure/release_check.py +186 -0
- package/src/people_network_memory/infrastructure/retrieval_intent.py +98 -0
- package/src/people_network_memory/infrastructure/semantic_index.py +262 -0
- package/src/people_network_memory/mcp_server/__init__.py +2 -0
- package/src/people_network_memory/mcp_server/contracts.py +85 -0
- package/src/people_network_memory/mcp_server/runtime.py +133 -0
- package/src/people_network_memory/mcp_server/tools.py +588 -0
- package/src/people_network_memory/ports/__init__.py +2 -0
- package/src/people_network_memory/ports/errors.py +25 -0
- package/src/people_network_memory/ports/interfaces.py +103 -0
- package/src/people_network_memory/projection/__init__.py +6 -0
- package/src/people_network_memory/projection/builders.py +46 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Resumable Graphiti indexing for canonical local interactions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Callable
|
|
10
|
+
|
|
11
|
+
from people_network_memory.config import PeopleMemoryConfig
|
|
12
|
+
from people_network_memory.domain.models import Evidence, SocialInteraction
|
|
13
|
+
from people_network_memory.graphiti_adapter.graphiti_store import GraphitiGraphStore
|
|
14
|
+
from people_network_memory.infrastructure.file_store import JsonPeopleStore, local_json_path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
IndexOne = Callable[[SocialInteraction], Evidence]
|
|
18
|
+
CacheOne = Callable[[SocialInteraction], None]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def index_local_json_to_graphiti(
|
|
22
|
+
config: PeopleMemoryConfig,
|
|
23
|
+
*,
|
|
24
|
+
source_data_path: str | Path | None = None,
|
|
25
|
+
state_path: str | Path | None = None,
|
|
26
|
+
limit: int | None = None,
|
|
27
|
+
resume: bool = True,
|
|
28
|
+
reset_state: bool = False,
|
|
29
|
+
continue_on_error: bool = False,
|
|
30
|
+
) -> dict[str, object]:
|
|
31
|
+
source_path = Path(source_data_path).expanduser() if source_data_path else local_json_path(config)
|
|
32
|
+
state = _state_file(config, state_path)
|
|
33
|
+
source_store = JsonPeopleStore(source_path)
|
|
34
|
+
graph_config = config if config.backend == "graphiti" else _as_graphiti_config(config)
|
|
35
|
+
graph_store = GraphitiGraphStore.from_config(graph_config)
|
|
36
|
+
try:
|
|
37
|
+
return index_interactions(
|
|
38
|
+
list(source_store.interactions.values()),
|
|
39
|
+
state_path=state,
|
|
40
|
+
source_path=source_path,
|
|
41
|
+
graphiti_kuzu_path=Path(graph_config.graphiti_kuzu_path).expanduser(),
|
|
42
|
+
limit=limit,
|
|
43
|
+
resume=resume,
|
|
44
|
+
reset_state=reset_state,
|
|
45
|
+
continue_on_error=continue_on_error,
|
|
46
|
+
index_one=graph_store.index_interaction_episode,
|
|
47
|
+
cache_one=graph_store.cache_interaction_projection,
|
|
48
|
+
)
|
|
49
|
+
finally:
|
|
50
|
+
graph_store.close()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def index_interactions(
|
|
54
|
+
interactions: list[SocialInteraction],
|
|
55
|
+
*,
|
|
56
|
+
state_path: Path,
|
|
57
|
+
source_path: Path,
|
|
58
|
+
graphiti_kuzu_path: Path,
|
|
59
|
+
limit: int | None,
|
|
60
|
+
resume: bool,
|
|
61
|
+
reset_state: bool,
|
|
62
|
+
continue_on_error: bool,
|
|
63
|
+
index_one: IndexOne,
|
|
64
|
+
cache_one: CacheOne | None = None,
|
|
65
|
+
) -> dict[str, object]:
|
|
66
|
+
if reset_state and state_path.exists():
|
|
67
|
+
state_path.unlink()
|
|
68
|
+
state = _load_state(state_path)
|
|
69
|
+
state.setdefault("source_path", str(source_path))
|
|
70
|
+
state.setdefault("graphiti_kuzu_path", str(graphiti_kuzu_path))
|
|
71
|
+
indexed = state.setdefault("indexed", {})
|
|
72
|
+
failures = state.setdefault("failures", [])
|
|
73
|
+
started_at = datetime.now(timezone.utc).isoformat()
|
|
74
|
+
attempted = 0
|
|
75
|
+
indexed_count = 0
|
|
76
|
+
skipped = 0
|
|
77
|
+
failed = 0
|
|
78
|
+
entries: list[dict[str, object]] = []
|
|
79
|
+
stop_reason = "complete"
|
|
80
|
+
for index, interaction in enumerate(interactions):
|
|
81
|
+
if limit is not None and attempted >= limit:
|
|
82
|
+
stop_reason = "limit_reached"
|
|
83
|
+
break
|
|
84
|
+
fingerprint = interaction_fingerprint(interaction)
|
|
85
|
+
if resume and fingerprint in indexed:
|
|
86
|
+
if cache_one is not None:
|
|
87
|
+
cache_one(interaction)
|
|
88
|
+
skipped += 1
|
|
89
|
+
continue
|
|
90
|
+
attempted += 1
|
|
91
|
+
entry: dict[str, object] = {
|
|
92
|
+
"interaction_index": index,
|
|
93
|
+
"fingerprint": fingerprint,
|
|
94
|
+
"source_text": interaction.source_text,
|
|
95
|
+
}
|
|
96
|
+
try:
|
|
97
|
+
evidence = index_one(interaction)
|
|
98
|
+
except Exception as exc: # pragma: no cover - concrete Graphiti failures are live
|
|
99
|
+
failed += 1
|
|
100
|
+
entry.update({"ok": False, "error": str(exc)})
|
|
101
|
+
failures.append(
|
|
102
|
+
{
|
|
103
|
+
**entry,
|
|
104
|
+
"failed_at": datetime.now(timezone.utc).isoformat(),
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
entries.append(entry)
|
|
108
|
+
_write_state(state_path, state)
|
|
109
|
+
if not continue_on_error:
|
|
110
|
+
stop_reason = "failed"
|
|
111
|
+
break
|
|
112
|
+
continue
|
|
113
|
+
indexed[fingerprint] = {
|
|
114
|
+
"interaction_index": index,
|
|
115
|
+
"evidence_id": evidence.evidence_id,
|
|
116
|
+
"indexed_at": datetime.now(timezone.utc).isoformat(),
|
|
117
|
+
}
|
|
118
|
+
if failures:
|
|
119
|
+
failures[:] = [
|
|
120
|
+
failure
|
|
121
|
+
for failure in failures
|
|
122
|
+
if failure.get("fingerprint") != fingerprint
|
|
123
|
+
]
|
|
124
|
+
if cache_one is not None:
|
|
125
|
+
cache_one(interaction)
|
|
126
|
+
indexed_count += 1
|
|
127
|
+
entry.update({"ok": True, "evidence_id": evidence.evidence_id})
|
|
128
|
+
entries.append(entry)
|
|
129
|
+
_write_state(state_path, state)
|
|
130
|
+
finished_at = datetime.now(timezone.utc).isoformat()
|
|
131
|
+
state["last_run"] = {
|
|
132
|
+
"started_at": started_at,
|
|
133
|
+
"finished_at": finished_at,
|
|
134
|
+
"attempted": attempted,
|
|
135
|
+
"indexed": indexed_count,
|
|
136
|
+
"skipped": skipped,
|
|
137
|
+
"failed": failed,
|
|
138
|
+
"stop_reason": stop_reason,
|
|
139
|
+
}
|
|
140
|
+
_write_state(state_path, state)
|
|
141
|
+
ok = failed == 0 and stop_reason in {"complete", "limit_reached"}
|
|
142
|
+
return {
|
|
143
|
+
"ok": ok,
|
|
144
|
+
"source_path": str(source_path),
|
|
145
|
+
"state_path": str(state_path),
|
|
146
|
+
"graphiti_kuzu_path": str(graphiti_kuzu_path),
|
|
147
|
+
"total_interactions": len(interactions),
|
|
148
|
+
"attempted": attempted,
|
|
149
|
+
"indexed": indexed_count,
|
|
150
|
+
"state_indexed_total": len(indexed),
|
|
151
|
+
"skipped": skipped,
|
|
152
|
+
"failed": failed,
|
|
153
|
+
"stop_reason": stop_reason,
|
|
154
|
+
"resume": resume,
|
|
155
|
+
"limit": limit,
|
|
156
|
+
"entries": entries,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def interaction_fingerprint(interaction: SocialInteraction) -> str:
|
|
161
|
+
serialized = interaction.model_dump_json()
|
|
162
|
+
return hashlib.sha256(serialized.encode("utf-8")).hexdigest()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _as_graphiti_config(config: PeopleMemoryConfig) -> PeopleMemoryConfig:
|
|
166
|
+
from dataclasses import replace
|
|
167
|
+
|
|
168
|
+
return replace(config, backend="graphiti")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _state_file(config: PeopleMemoryConfig, state_path: str | Path | None) -> Path:
|
|
172
|
+
if state_path:
|
|
173
|
+
return Path(state_path).expanduser()
|
|
174
|
+
return Path(config.data_path).expanduser() / "graphiti-index-state.json"
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _load_state(path: Path) -> dict[str, object]:
|
|
178
|
+
if not path.exists():
|
|
179
|
+
return {"schema_version": 1, "indexed": {}, "failures": []}
|
|
180
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
181
|
+
if not isinstance(payload, dict):
|
|
182
|
+
return {"schema_version": 1, "indexed": {}, "failures": []}
|
|
183
|
+
payload.setdefault("schema_version", 1)
|
|
184
|
+
payload.setdefault("indexed", {})
|
|
185
|
+
payload.setdefault("failures", [])
|
|
186
|
+
return payload
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _write_state(path: Path, state: dict[str, object]) -> None:
|
|
190
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
path.write_text(
|
|
192
|
+
json.dumps(state, ensure_ascii=False, indent=2, sort_keys=True),
|
|
193
|
+
encoding="utf-8",
|
|
194
|
+
)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Minimal social-memory ontology for Graphiti custom entity/edge mapping."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OntologyModel(BaseModel):
|
|
9
|
+
model_config = ConfigDict(extra="forbid")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Person(OntologyModel):
|
|
13
|
+
aliases: list[str] = []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Organization(OntologyModel):
|
|
17
|
+
organization_type: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Place(OntologyModel):
|
|
21
|
+
place_type: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SocialEvent(OntologyModel):
|
|
25
|
+
event_type: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TopicOrInterest(OntologyModel):
|
|
29
|
+
category: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MetWith(OntologyModel):
|
|
33
|
+
context: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MentionedPerson(OntologyModel):
|
|
37
|
+
context: str | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DiscussedTopic(OntologyModel):
|
|
41
|
+
context: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class WorksAt(OntologyModel):
|
|
45
|
+
role: str | None = None
|
|
46
|
+
is_current: bool | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class StudiedAt(OntologyModel):
|
|
50
|
+
degree: str | None = None
|
|
51
|
+
major: str | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
ENTITY_TYPES = {
|
|
55
|
+
"Person": Person,
|
|
56
|
+
"Organization": Organization,
|
|
57
|
+
"Place": Place,
|
|
58
|
+
"SocialEvent": SocialEvent,
|
|
59
|
+
"TopicOrInterest": TopicOrInterest,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
EDGE_TYPES = {
|
|
63
|
+
"MetWith": MetWith,
|
|
64
|
+
"MentionedPerson": MentionedPerson,
|
|
65
|
+
"DiscussedTopic": DiscussedTopic,
|
|
66
|
+
"WorksAt": WorksAt,
|
|
67
|
+
"StudiedAt": StudiedAt,
|
|
68
|
+
}
|