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.
- collab_memory-0.1.0/LICENSE +21 -0
- collab_memory-0.1.0/PKG-INFO +254 -0
- collab_memory-0.1.0/README.md +195 -0
- collab_memory-0.1.0/pyproject.toml +62 -0
- collab_memory-0.1.0/setup.cfg +4 -0
- collab_memory-0.1.0/src/collab_memory/__init__.py +0 -0
- collab_memory-0.1.0/src/collab_memory/adapters/__init__.py +7 -0
- collab_memory-0.1.0/src/collab_memory/adapters/chatgpt.py +134 -0
- collab_memory-0.1.0/src/collab_memory/adapters/claude.py +111 -0
- collab_memory-0.1.0/src/collab_memory/api/__init__.py +0 -0
- collab_memory-0.1.0/src/collab_memory/api/server.py +147 -0
- collab_memory-0.1.0/src/collab_memory/encoder.py +504 -0
- collab_memory-0.1.0/src/collab_memory/graph/__init__.py +20 -0
- collab_memory-0.1.0/src/collab_memory/graph/base.py +59 -0
- collab_memory-0.1.0/src/collab_memory/graph/graphiti_backend.py +365 -0
- collab_memory-0.1.0/src/collab_memory/graph/local.py +251 -0
- collab_memory-0.1.0/src/collab_memory/schema.py +180 -0
- collab_memory-0.1.0/src/collab_memory/scripts/__init__.py +11 -0
- collab_memory-0.1.0/src/collab_memory/scripts/serve.py +17 -0
- collab_memory-0.1.0/src/collab_memory.egg-info/PKG-INFO +254 -0
- collab_memory-0.1.0/src/collab_memory.egg-info/SOURCES.txt +26 -0
- collab_memory-0.1.0/src/collab_memory.egg-info/dependency_links.txt +1 -0
- collab_memory-0.1.0/src/collab_memory.egg-info/entry_points.txt +3 -0
- collab_memory-0.1.0/src/collab_memory.egg-info/requires.txt +16 -0
- collab_memory-0.1.0/src/collab_memory.egg-info/top_level.txt +1 -0
- collab_memory-0.1.0/tests/test_adapters.py +181 -0
- collab_memory-0.1.0/tests/test_encoder.py +327 -0
- 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 = ["."]
|
|
File without changes
|
|
@@ -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
|
+
]
|