@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.
Files changed (61) hide show
  1. package/README.md +476 -0
  2. package/docs/mcp_tools.md +138 -0
  3. package/harness_adapters/openclaw/mcp.managed.unix.template.json +25 -0
  4. package/harness_adapters/openclaw/mcp.managed.windows.template.json +26 -0
  5. package/harness_adapters/openclaw/mcp.template.json +14 -0
  6. package/harness_adapters/openclaw/ppl/SKILL.md +114 -0
  7. package/package.json +30 -0
  8. package/pyproject.toml +26 -0
  9. package/scripts/install_windows.ps1 +92 -0
  10. package/scripts/npm/people-memory.js +276 -0
  11. package/scripts/people_memory_bootstrap.py +247 -0
  12. package/scripts/run_graphiti_live_from_liepin.ps1 +87 -0
  13. package/scripts/run_tests_with_artifacts.ps1 +307 -0
  14. package/src/people_network_memory/__init__.py +6 -0
  15. package/src/people_network_memory/application/__init__.py +16 -0
  16. package/src/people_network_memory/application/normalization.py +1441 -0
  17. package/src/people_network_memory/application/services.py +921 -0
  18. package/src/people_network_memory/cli.py +1212 -0
  19. package/src/people_network_memory/config.py +268 -0
  20. package/src/people_network_memory/domain/__init__.py +55 -0
  21. package/src/people_network_memory/domain/identity.py +77 -0
  22. package/src/people_network_memory/domain/models.py +355 -0
  23. package/src/people_network_memory/fixtures/__init__.py +6 -0
  24. package/src/people_network_memory/fixtures/eval.py +398 -0
  25. package/src/people_network_memory/fixtures/extractor_eval.py +364 -0
  26. package/src/people_network_memory/fixtures/generator.py +290 -0
  27. package/src/people_network_memory/fixtures/report.py +252 -0
  28. package/src/people_network_memory/graphiti_adapter/__init__.py +9 -0
  29. package/src/people_network_memory/graphiti_adapter/episode_formatter.py +70 -0
  30. package/src/people_network_memory/graphiti_adapter/graphiti_store.py +655 -0
  31. package/src/people_network_memory/graphiti_adapter/indexer.py +194 -0
  32. package/src/people_network_memory/graphiti_adapter/ontology.py +68 -0
  33. package/src/people_network_memory/harness_adapters/__init__.py +2 -0
  34. package/src/people_network_memory/harness_adapters/openclaw/__init__.py +9 -0
  35. package/src/people_network_memory/harness_adapters/openclaw/installer.py +577 -0
  36. package/src/people_network_memory/harness_adapters/openclaw/integration_eval.py +508 -0
  37. package/src/people_network_memory/harness_adapters/openclaw/smoke.py +292 -0
  38. package/src/people_network_memory/infrastructure/__init__.py +2 -0
  39. package/src/people_network_memory/infrastructure/archive_backup.py +171 -0
  40. package/src/people_network_memory/infrastructure/diagnostics.py +171 -0
  41. package/src/people_network_memory/infrastructure/embeddings.py +155 -0
  42. package/src/people_network_memory/infrastructure/file_store.py +129 -0
  43. package/src/people_network_memory/infrastructure/graphiti_promotion.py +212 -0
  44. package/src/people_network_memory/infrastructure/id_generator.py +40 -0
  45. package/src/people_network_memory/infrastructure/in_memory_store.py +1008 -0
  46. package/src/people_network_memory/infrastructure/llm_extractor.py +476 -0
  47. package/src/people_network_memory/infrastructure/llm_identity_advisor.py +200 -0
  48. package/src/people_network_memory/infrastructure/llm_judge.py +162 -0
  49. package/src/people_network_memory/infrastructure/redaction.py +21 -0
  50. package/src/people_network_memory/infrastructure/release_check.py +186 -0
  51. package/src/people_network_memory/infrastructure/retrieval_intent.py +98 -0
  52. package/src/people_network_memory/infrastructure/semantic_index.py +262 -0
  53. package/src/people_network_memory/mcp_server/__init__.py +2 -0
  54. package/src/people_network_memory/mcp_server/contracts.py +85 -0
  55. package/src/people_network_memory/mcp_server/runtime.py +133 -0
  56. package/src/people_network_memory/mcp_server/tools.py +588 -0
  57. package/src/people_network_memory/ports/__init__.py +2 -0
  58. package/src/people_network_memory/ports/errors.py +25 -0
  59. package/src/people_network_memory/ports/interfaces.py +103 -0
  60. package/src/people_network_memory/projection/__init__.py +6 -0
  61. 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
+ }
@@ -0,0 +1,2 @@
1
+ """Harness adapter package placeholder."""
2
+
@@ -0,0 +1,9 @@
1
+ """OpenClaw adapter package."""
2
+
3
+ from people_network_memory.harness_adapters.openclaw.installer import (
4
+ SERVER_ID,
5
+ install_openclaw_adapter,
6
+ openclaw_checks,
7
+ )
8
+
9
+ __all__ = ["SERVER_ID", "install_openclaw_adapter", "openclaw_checks"]