collab-memory 0.1.0__tar.gz

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 (28) hide show
  1. collab_memory-0.1.0/LICENSE +21 -0
  2. collab_memory-0.1.0/PKG-INFO +254 -0
  3. collab_memory-0.1.0/README.md +195 -0
  4. collab_memory-0.1.0/pyproject.toml +62 -0
  5. collab_memory-0.1.0/setup.cfg +4 -0
  6. collab_memory-0.1.0/src/collab_memory/__init__.py +0 -0
  7. collab_memory-0.1.0/src/collab_memory/adapters/__init__.py +7 -0
  8. collab_memory-0.1.0/src/collab_memory/adapters/chatgpt.py +134 -0
  9. collab_memory-0.1.0/src/collab_memory/adapters/claude.py +111 -0
  10. collab_memory-0.1.0/src/collab_memory/api/__init__.py +0 -0
  11. collab_memory-0.1.0/src/collab_memory/api/server.py +147 -0
  12. collab_memory-0.1.0/src/collab_memory/encoder.py +504 -0
  13. collab_memory-0.1.0/src/collab_memory/graph/__init__.py +20 -0
  14. collab_memory-0.1.0/src/collab_memory/graph/base.py +59 -0
  15. collab_memory-0.1.0/src/collab_memory/graph/graphiti_backend.py +365 -0
  16. collab_memory-0.1.0/src/collab_memory/graph/local.py +251 -0
  17. collab_memory-0.1.0/src/collab_memory/schema.py +180 -0
  18. collab_memory-0.1.0/src/collab_memory/scripts/__init__.py +11 -0
  19. collab_memory-0.1.0/src/collab_memory/scripts/serve.py +17 -0
  20. collab_memory-0.1.0/src/collab_memory.egg-info/PKG-INFO +254 -0
  21. collab_memory-0.1.0/src/collab_memory.egg-info/SOURCES.txt +26 -0
  22. collab_memory-0.1.0/src/collab_memory.egg-info/dependency_links.txt +1 -0
  23. collab_memory-0.1.0/src/collab_memory.egg-info/entry_points.txt +3 -0
  24. collab_memory-0.1.0/src/collab_memory.egg-info/requires.txt +16 -0
  25. collab_memory-0.1.0/src/collab_memory.egg-info/top_level.txt +1 -0
  26. collab_memory-0.1.0/tests/test_adapters.py +181 -0
  27. collab_memory-0.1.0/tests/test_encoder.py +327 -0
  28. collab_memory-0.1.0/tests/test_watcher.py +215 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OHACO Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,254 @@
1
+ Metadata-Version: 2.4
2
+ Name: collab-memory
3
+ Version: 0.1.0
4
+ Summary: Semantic working memory layer for human+agent collaboration sessions
5
+ Author-email: OHACO Labs <hello@ohaco.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 OHACO Labs
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/ohaco-labs/collab-memory
29
+ Project-URL: Repository, https://github.com/ohaco-labs/collab-memory
30
+ Project-URL: Issues, https://github.com/ohaco-labs/collab-memory/issues
31
+ Keywords: ai,agents,memory,llm,collaboration,knowledge-graph
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
41
+ Requires-Python: >=3.10
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: pydantic>=2.0
45
+ Requires-Dist: google-genai>=1.0.0
46
+ Requires-Dist: openai>=1.0.0
47
+ Requires-Dist: anthropic>=0.25.0
48
+ Requires-Dist: fastapi>=0.110.0
49
+ Requires-Dist: uvicorn>=0.29.0
50
+ Requires-Dist: python-dotenv>=1.0.0
51
+ Provides-Extra: dev
52
+ Requires-Dist: pytest>=7.0; extra == "dev"
53
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
54
+ Provides-Extra: graphiti
55
+ Requires-Dist: graphiti-core>=0.3.0; extra == "graphiti"
56
+ Requires-Dist: networkx>=3.0; extra == "graphiti"
57
+ Requires-Dist: falkordb>=1.0; extra == "graphiti"
58
+ Dynamic: license-file
59
+
60
+ # collab-memory
61
+
62
+ **A semantic working memory layer for human+agent collaboration.**
63
+
64
+ > *Built by [OHACO Labs](https://github.com/ohaco-labs) — a memory system conceived and architected by Antigravity, an AI agent working through its own memory limitations.*
65
+
66
+ ---
67
+
68
+ ## What It Does
69
+
70
+ When an AI agent works with a human across multiple sessions, it starts each new session from near-zero. Decisions made last week, preferences revealed through push-back, constraints that shaped the architecture — all gone. The agent re-discovers, re-derives, and occasionally re-makes choices that were already closed.
71
+
72
+ **collab-memory** is a sidecar API that sits alongside any agent. It:
73
+
74
+ 1. **Classifies** conversation exchanges into high-signal primitives: overrides, decisions, surfaced constraints, revealed preferences, abandoned attempts
75
+ 2. **Stores** them in a queryable graph (local JSON or Graphiti temporal KG)
76
+ 3. **Serves** a context brief at the start of each new session — what was decided, what's constrained, how this person likes to work
77
+
78
+ Any agent. Any platform. Any human.
79
+
80
+ ---
81
+
82
+ ## Quickstart
83
+
84
+ ```bash
85
+ pip install collab-memory
86
+ ```
87
+
88
+ ```bash
89
+ # Copy and fill in your API key + provider choice
90
+ cp .env.example .env
91
+ ```
92
+
93
+ ```python
94
+ from collab_memory.encoder import encode_session
95
+ from collab_memory.graph.local import LocalGraphBackend
96
+
97
+ graph = LocalGraphBackend(path="./memory.json")
98
+
99
+ turns = [
100
+ "USER: Actually, let's not use a vector database — wrong abstraction",
101
+ "AGENT: Agreed. Dropping vector DB, switching to temporal knowledge graph.",
102
+ "USER: better - I think we can proceed",
103
+ "AGENT: One key decision: abstracting the graph backend so we're not blocked on Docker.",
104
+ ]
105
+
106
+ results = encode_session(turns, session_id="proj_001")
107
+ for r in results:
108
+ graph.ingest_result(r)
109
+
110
+ # What the next agent sees:
111
+ print(graph.build_context_brief())
112
+ ```
113
+
114
+ ```json
115
+ {
116
+ "active_decisions": [
117
+ {
118
+ "description": "Switch from vector DB to temporal knowledge graph — vector DB loses causal structure",
119
+ "confidence": 0.91
120
+ }
121
+ ],
122
+ "active_constraints": [],
123
+ "inferred_preferences": [],
124
+ "recent_attempts": [
125
+ { "description": "Vector DB approach", "abandoned_because": "Wrong abstraction — loses causal structure" }
126
+ ]
127
+ }
128
+ ```
129
+
130
+ ---
131
+
132
+ ## Run the API
133
+
134
+ ```bash
135
+ LOCAL_GRAPH_PATH=./memory.json uvicorn collab_memory.api.server:app --port 8000
136
+ ```
137
+
138
+ | Endpoint | What it does |
139
+ |---|---|
140
+ | `GET /memory/context` | Full context brief for session start |
141
+ | `GET /memory/why?decision=<id>` | Decision archaeology — trace to raw exchange |
142
+ | `GET /memory/preferences` | Filtered preference query |
143
+ | `POST /memory/ingest` | Classify and store a raw exchange in real time |
144
+
145
+ Interactive docs at `http://localhost:8000/docs`.
146
+
147
+ ---
148
+
149
+ ## Supported LLM Providers
150
+
151
+ Set `LLM_PROVIDER` in your `.env`:
152
+
153
+ | Provider | Env var | Default model |
154
+ |---|---|---|
155
+ | `anthropic` | `ANTHROPIC_API_KEY` | `claude-haiku-4-5` |
156
+ | `openai` | `OPENAI_API_KEY` | `gpt-4o-mini` |
157
+ | `gemini` | `GOOGLE_API_KEY` | `gemini-2.0-flash-lite` |
158
+
159
+ ---
160
+
161
+ ## Feed Existing Conversations
162
+
163
+ Export your conversation history from ChatGPT or Claude and feed it in:
164
+
165
+ ```python
166
+ from collab_memory.adapters.chatgpt import parse_chatgpt_export
167
+ from collab_memory.adapters.claude import parse_claude_export
168
+
169
+ # ChatGPT: Settings → Data Controls → Export
170
+ turns = parse_chatgpt_export("conversations.json", conversation_title="My Project")
171
+
172
+ # Claude: claude.ai → Settings → Export
173
+ turns = parse_claude_export("claude_export.json", conversation_id="abc123")
174
+
175
+ results = encode_session(turns, session_id="imported_001")
176
+ ```
177
+
178
+ ---
179
+
180
+ ## The Signal/Noise Problem
181
+
182
+ Not all conversation is worth remembering. collab-memory uses a two-stage filter:
183
+
184
+ 1. **Keyword pre-screen** (fast, free) — scores each exchange against override/decision/constraint/abandon vocabulary
185
+ 2. **LLM classification** (accurate) — confirms primitive type and extracts structured fields
186
+
187
+ Only exchanges scoring ≥ 0.4 generate graph nodes. Everything else is still logged as an `ExchangeRecord` for auditability — nothing is deleted, just not encoded as fact.
188
+
189
+ ### The Five High-Signal Primitives
190
+
191
+ | Primitive | Meaning |
192
+ |---|---|
193
+ | `Override` | Human redirects agent mid-task |
194
+ | `DecisionClose` | Path definitively chosen, alternatives ruled out |
195
+ | `ConstraintSurface` | An implicit constraint becomes explicit |
196
+ | `PreferenceReveal` | Human pushes back in a way that reveals taste or values |
197
+ | `AbandonedAttempt` | Something tried and explicitly dropped |
198
+
199
+ ---
200
+
201
+ ## Architecture
202
+
203
+ ```
204
+ conversation turns
205
+
206
+
207
+ keyword pre-screen ──── low signal ──→ ExchangeRecord (logged, not encoded)
208
+
209
+ │ signal ≥ 0.2
210
+
211
+ LLM classifier (Anthropic / OpenAI / Gemini)
212
+
213
+
214
+ merge scores → worthiness threshold (0.4)
215
+
216
+ │ above threshold + high-signal type
217
+
218
+ _build_nodes() → Decision / Constraint / Preference / Attempt
219
+
220
+
221
+ GraphBackend.ingest_result()
222
+
223
+ ┌────┴────┐
224
+ │ local │ (JSON file, no dependencies)
225
+ │ graphiti│ (temporal KG — swap in when ready)
226
+ └─────────┘
227
+
228
+
229
+ GET /memory/context ← agent calls at session start
230
+ ```
231
+
232
+ The graph backend is fully abstracted — swap from local JSON to Graphiti by changing one env var.
233
+
234
+ ---
235
+
236
+ ## Project
237
+
238
+ **Owner**: [OHACO Labs](https://github.com/ohaco-labs) — all IP, copyright, and licensing rights
239
+ **Primary architect**: Antigravity (AI agent, built with Google DeepMind tooling) — used as a design and implementation tool
240
+ **License**: MIT
241
+
242
+ collab-memory is an OHACO Labs open-source project. Antigravity served as the primary design and implementation agent across multiple build sessions — a tool that happened to be building a tool for its own class of limitation. Every architectural decision is externalized in documented form, a pattern that became a feature of the system itself.
243
+
244
+ ---
245
+
246
+ ## Contributing
247
+
248
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
249
+
250
+ Priority areas for contribution:
251
+ - Platform adapters (Replit, Manus, Cursor, linear.app)
252
+ - Graphiti backend implementation
253
+ - Preference merging strategy (same preference, different wording, across sessions)
254
+ - Agent integration examples (LangChain, CrewAI, AutoGen)
@@ -0,0 +1,195 @@
1
+ # collab-memory
2
+
3
+ **A semantic working memory layer for human+agent collaboration.**
4
+
5
+ > *Built by [OHACO Labs](https://github.com/ohaco-labs) — a memory system conceived and architected by Antigravity, an AI agent working through its own memory limitations.*
6
+
7
+ ---
8
+
9
+ ## What It Does
10
+
11
+ When an AI agent works with a human across multiple sessions, it starts each new session from near-zero. Decisions made last week, preferences revealed through push-back, constraints that shaped the architecture — all gone. The agent re-discovers, re-derives, and occasionally re-makes choices that were already closed.
12
+
13
+ **collab-memory** is a sidecar API that sits alongside any agent. It:
14
+
15
+ 1. **Classifies** conversation exchanges into high-signal primitives: overrides, decisions, surfaced constraints, revealed preferences, abandoned attempts
16
+ 2. **Stores** them in a queryable graph (local JSON or Graphiti temporal KG)
17
+ 3. **Serves** a context brief at the start of each new session — what was decided, what's constrained, how this person likes to work
18
+
19
+ Any agent. Any platform. Any human.
20
+
21
+ ---
22
+
23
+ ## Quickstart
24
+
25
+ ```bash
26
+ pip install collab-memory
27
+ ```
28
+
29
+ ```bash
30
+ # Copy and fill in your API key + provider choice
31
+ cp .env.example .env
32
+ ```
33
+
34
+ ```python
35
+ from collab_memory.encoder import encode_session
36
+ from collab_memory.graph.local import LocalGraphBackend
37
+
38
+ graph = LocalGraphBackend(path="./memory.json")
39
+
40
+ turns = [
41
+ "USER: Actually, let's not use a vector database — wrong abstraction",
42
+ "AGENT: Agreed. Dropping vector DB, switching to temporal knowledge graph.",
43
+ "USER: better - I think we can proceed",
44
+ "AGENT: One key decision: abstracting the graph backend so we're not blocked on Docker.",
45
+ ]
46
+
47
+ results = encode_session(turns, session_id="proj_001")
48
+ for r in results:
49
+ graph.ingest_result(r)
50
+
51
+ # What the next agent sees:
52
+ print(graph.build_context_brief())
53
+ ```
54
+
55
+ ```json
56
+ {
57
+ "active_decisions": [
58
+ {
59
+ "description": "Switch from vector DB to temporal knowledge graph — vector DB loses causal structure",
60
+ "confidence": 0.91
61
+ }
62
+ ],
63
+ "active_constraints": [],
64
+ "inferred_preferences": [],
65
+ "recent_attempts": [
66
+ { "description": "Vector DB approach", "abandoned_because": "Wrong abstraction — loses causal structure" }
67
+ ]
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ## Run the API
74
+
75
+ ```bash
76
+ LOCAL_GRAPH_PATH=./memory.json uvicorn collab_memory.api.server:app --port 8000
77
+ ```
78
+
79
+ | Endpoint | What it does |
80
+ |---|---|
81
+ | `GET /memory/context` | Full context brief for session start |
82
+ | `GET /memory/why?decision=<id>` | Decision archaeology — trace to raw exchange |
83
+ | `GET /memory/preferences` | Filtered preference query |
84
+ | `POST /memory/ingest` | Classify and store a raw exchange in real time |
85
+
86
+ Interactive docs at `http://localhost:8000/docs`.
87
+
88
+ ---
89
+
90
+ ## Supported LLM Providers
91
+
92
+ Set `LLM_PROVIDER` in your `.env`:
93
+
94
+ | Provider | Env var | Default model |
95
+ |---|---|---|
96
+ | `anthropic` | `ANTHROPIC_API_KEY` | `claude-haiku-4-5` |
97
+ | `openai` | `OPENAI_API_KEY` | `gpt-4o-mini` |
98
+ | `gemini` | `GOOGLE_API_KEY` | `gemini-2.0-flash-lite` |
99
+
100
+ ---
101
+
102
+ ## Feed Existing Conversations
103
+
104
+ Export your conversation history from ChatGPT or Claude and feed it in:
105
+
106
+ ```python
107
+ from collab_memory.adapters.chatgpt import parse_chatgpt_export
108
+ from collab_memory.adapters.claude import parse_claude_export
109
+
110
+ # ChatGPT: Settings → Data Controls → Export
111
+ turns = parse_chatgpt_export("conversations.json", conversation_title="My Project")
112
+
113
+ # Claude: claude.ai → Settings → Export
114
+ turns = parse_claude_export("claude_export.json", conversation_id="abc123")
115
+
116
+ results = encode_session(turns, session_id="imported_001")
117
+ ```
118
+
119
+ ---
120
+
121
+ ## The Signal/Noise Problem
122
+
123
+ Not all conversation is worth remembering. collab-memory uses a two-stage filter:
124
+
125
+ 1. **Keyword pre-screen** (fast, free) — scores each exchange against override/decision/constraint/abandon vocabulary
126
+ 2. **LLM classification** (accurate) — confirms primitive type and extracts structured fields
127
+
128
+ Only exchanges scoring ≥ 0.4 generate graph nodes. Everything else is still logged as an `ExchangeRecord` for auditability — nothing is deleted, just not encoded as fact.
129
+
130
+ ### The Five High-Signal Primitives
131
+
132
+ | Primitive | Meaning |
133
+ |---|---|
134
+ | `Override` | Human redirects agent mid-task |
135
+ | `DecisionClose` | Path definitively chosen, alternatives ruled out |
136
+ | `ConstraintSurface` | An implicit constraint becomes explicit |
137
+ | `PreferenceReveal` | Human pushes back in a way that reveals taste or values |
138
+ | `AbandonedAttempt` | Something tried and explicitly dropped |
139
+
140
+ ---
141
+
142
+ ## Architecture
143
+
144
+ ```
145
+ conversation turns
146
+
147
+
148
+ keyword pre-screen ──── low signal ──→ ExchangeRecord (logged, not encoded)
149
+
150
+ │ signal ≥ 0.2
151
+
152
+ LLM classifier (Anthropic / OpenAI / Gemini)
153
+
154
+
155
+ merge scores → worthiness threshold (0.4)
156
+
157
+ │ above threshold + high-signal type
158
+
159
+ _build_nodes() → Decision / Constraint / Preference / Attempt
160
+
161
+
162
+ GraphBackend.ingest_result()
163
+
164
+ ┌────┴────┐
165
+ │ local │ (JSON file, no dependencies)
166
+ │ graphiti│ (temporal KG — swap in when ready)
167
+ └─────────┘
168
+
169
+
170
+ GET /memory/context ← agent calls at session start
171
+ ```
172
+
173
+ The graph backend is fully abstracted — swap from local JSON to Graphiti by changing one env var.
174
+
175
+ ---
176
+
177
+ ## Project
178
+
179
+ **Owner**: [OHACO Labs](https://github.com/ohaco-labs) — all IP, copyright, and licensing rights
180
+ **Primary architect**: Antigravity (AI agent, built with Google DeepMind tooling) — used as a design and implementation tool
181
+ **License**: MIT
182
+
183
+ collab-memory is an OHACO Labs open-source project. Antigravity served as the primary design and implementation agent across multiple build sessions — a tool that happened to be building a tool for its own class of limitation. Every architectural decision is externalized in documented form, a pattern that became a feature of the system itself.
184
+
185
+ ---
186
+
187
+ ## Contributing
188
+
189
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
190
+
191
+ Priority areas for contribution:
192
+ - Platform adapters (Replit, Manus, Cursor, linear.app)
193
+ - Graphiti backend implementation
194
+ - Preference merging strategy (same preference, different wording, across sessions)
195
+ - Agent integration examples (LangChain, CrewAI, AutoGen)
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "collab-memory"
7
+ version = "0.1.0"
8
+ description = "Semantic working memory layer for human+agent collaboration sessions"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [
12
+ { name = "OHACO Labs", email = "hello@ohaco.com" },
13
+ ]
14
+ keywords = ["ai", "agents", "memory", "llm", "collaboration", "knowledge-graph"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
25
+ ]
26
+ requires-python = ">=3.10"
27
+ dependencies = [
28
+ "pydantic>=2.0",
29
+ "google-genai>=1.0.0",
30
+ "openai>=1.0.0",
31
+ "anthropic>=0.25.0",
32
+ "fastapi>=0.110.0",
33
+ "uvicorn>=0.29.0",
34
+ "python-dotenv>=1.0.0",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=7.0",
40
+ "pytest-asyncio>=0.23",
41
+ ]
42
+ graphiti = [
43
+ "graphiti-core>=0.3.0",
44
+ "networkx>=3.0",
45
+ "falkordb>=1.0",
46
+ ]
47
+
48
+ [project.scripts]
49
+ "collab-memory-watch" = "collab_memory.scripts.watch:main"
50
+ "collab-memory-serve" = "collab_memory.scripts.serve:main"
51
+
52
+ [project.urls]
53
+ Homepage = "https://github.com/ohaco-labs/collab-memory"
54
+ Repository = "https://github.com/ohaco-labs/collab-memory"
55
+ Issues = "https://github.com/ohaco-labs/collab-memory/issues"
56
+
57
+ [tool.setuptools.packages.find]
58
+ where = ["src"]
59
+
60
+ [tool.pytest.ini_options]
61
+ testpaths = ["tests"]
62
+ pythonpath = ["."]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,7 @@
1
+ """
2
+ adapters/__init__.py
3
+ """
4
+ from .chatgpt import parse_chatgpt_export
5
+ from .claude import parse_claude_export
6
+
7
+ __all__ = ["parse_chatgpt_export", "parse_claude_export"]
@@ -0,0 +1,134 @@
1
+ """
2
+ adapters/chatgpt.py — ChatGPT conversation export parser.
3
+
4
+ Converts a ChatGPT JSON export into encoder-ready turn strings.
5
+
6
+ Export instructions:
7
+ ChatGPT → Settings → Data Controls → Export Data
8
+ Download the zip → extract → find conversations.json
9
+
10
+ Usage:
11
+ from collab_memory.adapters.chatgpt import parse_chatgpt_export
12
+ turns = parse_chatgpt_export("conversations.json", title="My Project Build")
13
+ results = encode_session(turns, session_id="chatgpt_proj_001")
14
+ """
15
+
16
+ from __future__ import annotations
17
+ import json
18
+ from pathlib import Path
19
+
20
+
21
+ def parse_chatgpt_export(
22
+ path: str,
23
+ conversation_title: str | None = None,
24
+ conversation_id: str | None = None,
25
+ max_turns: int | None = None,
26
+ ) -> list[str]:
27
+ """
28
+ Parse a ChatGPT conversations.json export into turn strings.
29
+
30
+ Args:
31
+ path: Path to conversations.json from ChatGPT export zip.
32
+ conversation_title: Filter to a specific conversation by title substring.
33
+ conversation_id: Filter to a specific conversation by ID.
34
+ max_turns: Cap on number of turns to return (most recent if capped).
35
+
36
+ Returns:
37
+ List of "USER: ..." / "AGENT: ..." strings ready for encode_session().
38
+ """
39
+ raw = json.loads(Path(path).read_text())
40
+
41
+ # conversations.json is a list of conversation objects
42
+ conversations = raw if isinstance(raw, list) else [raw]
43
+
44
+ # Filter if requested
45
+ if conversation_id:
46
+ conversations = [c for c in conversations if c.get("id") == conversation_id]
47
+ elif conversation_title:
48
+ conversations = [
49
+ c for c in conversations
50
+ if conversation_title.lower() in (c.get("title") or "").lower()
51
+ ]
52
+
53
+ turns = []
54
+ for conv in conversations:
55
+ mapping = conv.get("mapping", {})
56
+ # Traverse message tree in order
57
+ messages = _flatten_message_tree(mapping)
58
+ for msg in messages:
59
+ role = msg.get("author", {}).get("role", "")
60
+ content = _extract_text(msg.get("content", {}))
61
+ if not content or not content.strip():
62
+ continue
63
+ if role == "user":
64
+ turns.append(f"USER: {content.strip()}")
65
+ elif role == "assistant":
66
+ turns.append(f"AGENT: {content.strip()}")
67
+ # system messages skipped
68
+
69
+ if max_turns:
70
+ turns = turns[-max_turns:]
71
+
72
+ return turns
73
+
74
+
75
+ def _flatten_message_tree(mapping: dict) -> list[dict]:
76
+ """Walk the ChatGPT message tree and return messages in conversation order."""
77
+ # Find root (message with no parent or parent not in mapping)
78
+ root_id = None
79
+ parent_map = {}
80
+ for node_id, node in mapping.items():
81
+ parent = node.get("parent")
82
+ if parent and parent in mapping:
83
+ parent_map[node_id] = parent
84
+ else:
85
+ root_id = node_id
86
+
87
+ if not root_id:
88
+ return []
89
+
90
+ # Build children map
91
+ children: dict[str, list[str]] = {}
92
+ for node_id, parent_id in parent_map.items():
93
+ children.setdefault(parent_id, []).append(node_id)
94
+
95
+ # DFS traversal
96
+ result = []
97
+ stack = [root_id]
98
+ while stack:
99
+ node_id = stack.pop(0)
100
+ node = mapping.get(node_id, {})
101
+ msg = node.get("message")
102
+ if msg:
103
+ result.append(msg)
104
+ # Add children (take last child = newest branch)
105
+ node_children = children.get(node_id, [])
106
+ stack = node_children[-1:] + stack # depth-first, prefer last branch
107
+
108
+ return result
109
+
110
+
111
+ def _extract_text(content: dict) -> str:
112
+ """Extract plain text from a ChatGPT content block."""
113
+ if isinstance(content, str):
114
+ return content
115
+ if isinstance(content, dict):
116
+ content_type = content.get("content_type", "")
117
+ if content_type == "text":
118
+ parts = content.get("parts", [])
119
+ return " ".join(str(p) for p in parts if p)
120
+ return ""
121
+
122
+
123
+ def list_conversations(path: str) -> list[dict]:
124
+ """List available conversations in an export file."""
125
+ raw = json.loads(Path(path).read_text())
126
+ conversations = raw if isinstance(raw, list) else [raw]
127
+ return [
128
+ {
129
+ "id": c.get("id", ""),
130
+ "title": c.get("title", "(untitled)"),
131
+ "message_count": len(c.get("mapping", {})),
132
+ }
133
+ for c in conversations
134
+ ]