ompa 0.2.0__tar.gz → 0.2.2__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.
- {ompa-0.2.0 → ompa-0.2.2}/PKG-INFO +26 -29
- {ompa-0.2.0 → ompa-0.2.2}/README.md +22 -21
- {ompa-0.2.0 → ompa-0.2.2}/ompa/__init__.py +1 -1
- {ompa-0.2.0 → ompa-0.2.2}/ompa/__main__.py +2 -1
- {ompa-0.2.0 → ompa-0.2.2}/ompa/classifier.py +16 -15
- {ompa-0.2.0 → ompa-0.2.2}/ompa/cli.py +27 -20
- {ompa-0.2.0 → ompa-0.2.2}/ompa/core.py +14 -5
- {ompa-0.2.0 → ompa-0.2.2}/ompa/hooks.py +167 -128
- ompa-0.2.2/ompa/knowledge_graph.py +291 -0
- {ompa-0.2.0 → ompa-0.2.2}/ompa/mcp_server.py +161 -243
- {ompa-0.2.0 → ompa-0.2.2}/ompa/palace.py +71 -46
- {ompa-0.2.0 → ompa-0.2.2}/ompa/semantic.py +127 -82
- {ompa-0.2.0 → ompa-0.2.2}/ompa/vault.py +155 -61
- {ompa-0.2.0 → ompa-0.2.2}/ompa.egg-info/PKG-INFO +26 -29
- {ompa-0.2.0 → ompa-0.2.2}/ompa.egg-info/requires.txt +1 -4
- {ompa-0.2.0 → ompa-0.2.2}/pyproject.toml +5 -16
- ompa-0.2.2/tests/test_ompa.py +398 -0
- ompa-0.2.0/ompa/knowledge_graph.py +0 -303
- ompa-0.2.0/tests/test_ompa.py +0 -213
- {ompa-0.2.0 → ompa-0.2.2}/LICENSE +0 -0
- {ompa-0.2.0 → ompa-0.2.2}/ompa/py.typed +0 -0
- {ompa-0.2.0 → ompa-0.2.2}/ompa.egg-info/SOURCES.txt +0 -0
- {ompa-0.2.0 → ompa-0.2.2}/ompa.egg-info/dependency_links.txt +0 -0
- {ompa-0.2.0 → ompa-0.2.2}/ompa.egg-info/entry_points.txt +0 -0
- {ompa-0.2.0 → ompa-0.2.2}/ompa.egg-info/top_level.txt +0 -0
- {ompa-0.2.0 → ompa-0.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ompa
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Universal AI agent memory layer — vault + palace + temporal knowledge graph
|
|
5
5
|
Author-email: Micap AI <info@micap.ai>
|
|
6
6
|
License: MIT
|
|
@@ -8,15 +8,14 @@ Project-URL: Homepage, https://github.com/jmiaie/ompa
|
|
|
8
8
|
Project-URL: Repository, https://github.com/jmiaie/ompa
|
|
9
9
|
Project-URL: Documentation, https://github.com/jmiaie/ompa#readme
|
|
10
10
|
Project-URL: Bug Tracker, https://github.com/jmiaie/ompa/issues
|
|
11
|
-
Keywords: ai,
|
|
11
|
+
Keywords: ai,agent,memory,obsidian,knowledge-graph,mcp,claude,llm,rag,semantic-search
|
|
12
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
|
-
Classifier: Environment :: Console
|
|
14
13
|
Classifier: Intended Audience :: Developers
|
|
15
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
19
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
21
|
Requires-Python: >=3.10
|
|
@@ -25,7 +24,6 @@ License-File: LICENSE
|
|
|
25
24
|
Requires-Dist: typer>=0.9.0
|
|
26
25
|
Requires-Dist: rich>=13.0.0
|
|
27
26
|
Requires-Dist: python-frontmatter>=1.1.0
|
|
28
|
-
Requires-Dist: watchdog>=3.0.0
|
|
29
27
|
Provides-Extra: dev
|
|
30
28
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
29
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
@@ -35,13 +33,15 @@ Provides-Extra: semantic
|
|
|
35
33
|
Requires-Dist: numpy>=1.24.0; extra == "semantic"
|
|
36
34
|
Requires-Dist: sentence-transformers>=2.2.0; extra == "semantic"
|
|
37
35
|
Provides-Extra: all
|
|
38
|
-
Requires-Dist:
|
|
39
|
-
Requires-Dist: sentence-transformers>=2.2.0; extra == "all"
|
|
40
|
-
Requires-Dist: chromadb>=0.4.0; extra == "all"
|
|
36
|
+
Requires-Dist: ompa[semantic]; extra == "all"
|
|
41
37
|
Dynamic: license-file
|
|
42
38
|
|
|
43
39
|
# OMPA
|
|
44
40
|
|
|
41
|
+
[](https://pypi.org/project/ompa/)
|
|
42
|
+
[](https://pypi.org/project/ompa/)
|
|
43
|
+
[](https://github.com/jmiaie/ompa/blob/main/LICENSE)
|
|
44
|
+
|
|
45
45
|
> **Obsidian-MemPalace-Agnostic** — Universal AI agent memory layer
|
|
46
46
|
|
|
47
47
|
OMPA gives any AI agent persistent memory with vault conventions, palace navigation, and a temporal knowledge graph.
|
|
@@ -57,10 +57,6 @@ This project is a synthesis of ideas and code from the AI agent memory community
|
|
|
57
57
|
|
|
58
58
|
OMPA combines these into a framework-agnostic package that works with any AI agent runtime.
|
|
59
59
|
|
|
60
|
-
[](https://pypi.org/project/ompa/)
|
|
61
|
-
[](https://pypi.org/project/ompa/)
|
|
62
|
-
[](https://opensource.org/licenses/MIT)
|
|
63
|
-
|
|
64
60
|
## The Problem
|
|
65
61
|
|
|
66
62
|
Every AI agent starts empty every session. Important decisions get lost. Context grows expensive. `ANKI` prompts only get you so far.
|
|
@@ -78,11 +74,13 @@ OMPA gives any AI agent — **Claude Code, OpenClaw, Codex, Gemini CLI, or any c
|
|
|
78
74
|
|
|
79
75
|
```bash
|
|
80
76
|
pip install ompa
|
|
77
|
+
ao init
|
|
78
|
+
ao status
|
|
79
|
+
```
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
ao init ./workspace
|
|
81
|
+
That's it -- you have a vault with persistent memory. Now use it in a session:
|
|
84
82
|
|
|
85
|
-
|
|
83
|
+
```bash
|
|
86
84
|
ao session-start # ~2K token context injection
|
|
87
85
|
ao classify "We decided to go with Postgres" # Route to right folder
|
|
88
86
|
ao search "authentication decisions" # Semantic search
|
|
@@ -122,7 +120,7 @@ ao wrap-up # Session summary + save to vault
|
|
|
122
120
|
|
|
123
121
|
### 15 Message Types
|
|
124
122
|
|
|
125
|
-
DECISION, INCIDENT, WIN,
|
|
123
|
+
DECISION, INCIDENT, WIN, LOSS, BLOCKER, QUESTION, SUGGESTION, REVIEW, BUG, FEATURE, LEARN, RETROSPECTIVE, ALERT, STATUS, CHORE — each with routing hints that automatically file things in the right place.
|
|
126
124
|
|
|
127
125
|
### Semantic Search (Zero API Cost)
|
|
128
126
|
|
|
@@ -137,13 +135,13 @@ ompa/
|
|
|
137
135
|
├── palace.py # Palace metadata (wings/rooms/drawers)
|
|
138
136
|
├── knowledge_graph.py # Temporal KG (SQLite triples)
|
|
139
137
|
├── hooks.py # 5 lifecycle hooks
|
|
140
|
-
├── classifier.py
|
|
138
|
+
├── classifier.py # 15 message types
|
|
141
139
|
├── semantic.py # Local semantic search
|
|
142
140
|
├── mcp_server.py # MCP protocol server (14 tools)
|
|
143
|
-
└── cli.py
|
|
141
|
+
└── cli.py # CLI commands
|
|
144
142
|
```
|
|
145
143
|
|
|
146
|
-
## MCP Server (
|
|
144
|
+
## MCP Server (15 Tools)
|
|
147
145
|
|
|
148
146
|
Works with **Claude Desktop, Cursor, Windsurf** natively:
|
|
149
147
|
|
|
@@ -152,7 +150,7 @@ Works with **Claude Desktop, Cursor, Windsurf** natively:
|
|
|
152
150
|
claude mcp add ompa -- python -m ompa.mcp_server
|
|
153
151
|
```
|
|
154
152
|
|
|
155
|
-
Tools: `ao_session_start`, `ao_classify`, `ao_search`, `ao_kg_query`, `ao_kg_add`, `
|
|
153
|
+
Tools: `ao_session_start`, `ao_classify`, `ao_search`, `ao_kg_query`, `ao_kg_add`, `ao_palace_wings`, `ao_palace_rooms`, `ao_palace_tunnel`, `ao_validate`, `ao_wrap_up`, `ao_status`, `ao_orphans`, `ao_init`, `ao_search`, `ao_stop`
|
|
156
154
|
|
|
157
155
|
## Python API
|
|
158
156
|
|
|
@@ -205,9 +203,8 @@ Unlike MemPalace (Claude Code + MCP only) or obsidian-mind (Claude Code hooks on
|
|
|
205
203
|
## Installation
|
|
206
204
|
|
|
207
205
|
```bash
|
|
208
|
-
pip install ompa
|
|
209
|
-
pip install ompa[
|
|
210
|
-
pip install ompa[all] # All optional dependencies
|
|
206
|
+
pip install ompa # Core only
|
|
207
|
+
pip install ompa[all] # All dependencies including sentence-transformers
|
|
211
208
|
```
|
|
212
209
|
|
|
213
210
|
Requires Python 3.10+.
|
|
@@ -219,17 +216,17 @@ Because memory should not be coupled to your agent framework. Build once, use an
|
|
|
219
216
|
## Comparison
|
|
220
217
|
|
|
221
218
|
| Feature | OMPA | MemPalace | obsidian-mind |
|
|
222
|
-
|
|
219
|
+
|---------|-----------------|------------|---------------|
|
|
223
220
|
| Framework | Any | Claude Code | Claude Code |
|
|
224
221
|
| Memory type | Vault + Palace + KG | Palace + KG | Vault only |
|
|
225
222
|
| Semantic search | Local (free) | ChromaDB API | QMD (paid) |
|
|
226
|
-
| Temporal KG | SQLite | SQLite |
|
|
227
|
-
| MCP server |
|
|
228
|
-
| CLI |
|
|
223
|
+
| Temporal KG | SQLite ✓ | SQLite ✓ | ✗ |
|
|
224
|
+
| MCP server | 15 tools | 15 tools | ✗ |
|
|
225
|
+
| CLI | 14 commands | ✗ | ✗ |
|
|
229
226
|
| Hooks | 5 lifecycle | 3 lifecycle | 3 lifecycle |
|
|
230
227
|
| Message types | 15 | 15 | 5 |
|
|
231
|
-
| Verbatim storage |
|
|
232
|
-
| Multi-agent |
|
|
228
|
+
| Verbatim storage | ✓ | ✓ | ✗ |
|
|
229
|
+
| Multi-agent | ✓ | ✗ | ✗ |
|
|
233
230
|
|
|
234
231
|
## License
|
|
235
232
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# OMPA
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/ompa/)
|
|
4
|
+
[](https://pypi.org/project/ompa/)
|
|
5
|
+
[](https://github.com/jmiaie/ompa/blob/main/LICENSE)
|
|
6
|
+
|
|
3
7
|
> **Obsidian-MemPalace-Agnostic** — Universal AI agent memory layer
|
|
4
8
|
|
|
5
9
|
OMPA gives any AI agent persistent memory with vault conventions, palace navigation, and a temporal knowledge graph.
|
|
@@ -15,10 +19,6 @@ This project is a synthesis of ideas and code from the AI agent memory community
|
|
|
15
19
|
|
|
16
20
|
OMPA combines these into a framework-agnostic package that works with any AI agent runtime.
|
|
17
21
|
|
|
18
|
-
[](https://pypi.org/project/ompa/)
|
|
19
|
-
[](https://pypi.org/project/ompa/)
|
|
20
|
-
[](https://opensource.org/licenses/MIT)
|
|
21
|
-
|
|
22
22
|
## The Problem
|
|
23
23
|
|
|
24
24
|
Every AI agent starts empty every session. Important decisions get lost. Context grows expensive. `ANKI` prompts only get you so far.
|
|
@@ -36,11 +36,13 @@ OMPA gives any AI agent — **Claude Code, OpenClaw, Codex, Gemini CLI, or any c
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
pip install ompa
|
|
39
|
+
ao init
|
|
40
|
+
ao status
|
|
41
|
+
```
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
ao init ./workspace
|
|
43
|
+
That's it -- you have a vault with persistent memory. Now use it in a session:
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
```bash
|
|
44
46
|
ao session-start # ~2K token context injection
|
|
45
47
|
ao classify "We decided to go with Postgres" # Route to right folder
|
|
46
48
|
ao search "authentication decisions" # Semantic search
|
|
@@ -80,7 +82,7 @@ ao wrap-up # Session summary + save to vault
|
|
|
80
82
|
|
|
81
83
|
### 15 Message Types
|
|
82
84
|
|
|
83
|
-
DECISION, INCIDENT, WIN,
|
|
85
|
+
DECISION, INCIDENT, WIN, LOSS, BLOCKER, QUESTION, SUGGESTION, REVIEW, BUG, FEATURE, LEARN, RETROSPECTIVE, ALERT, STATUS, CHORE — each with routing hints that automatically file things in the right place.
|
|
84
86
|
|
|
85
87
|
### Semantic Search (Zero API Cost)
|
|
86
88
|
|
|
@@ -95,13 +97,13 @@ ompa/
|
|
|
95
97
|
├── palace.py # Palace metadata (wings/rooms/drawers)
|
|
96
98
|
├── knowledge_graph.py # Temporal KG (SQLite triples)
|
|
97
99
|
├── hooks.py # 5 lifecycle hooks
|
|
98
|
-
├── classifier.py
|
|
100
|
+
├── classifier.py # 15 message types
|
|
99
101
|
├── semantic.py # Local semantic search
|
|
100
102
|
├── mcp_server.py # MCP protocol server (14 tools)
|
|
101
|
-
└── cli.py
|
|
103
|
+
└── cli.py # CLI commands
|
|
102
104
|
```
|
|
103
105
|
|
|
104
|
-
## MCP Server (
|
|
106
|
+
## MCP Server (15 Tools)
|
|
105
107
|
|
|
106
108
|
Works with **Claude Desktop, Cursor, Windsurf** natively:
|
|
107
109
|
|
|
@@ -110,7 +112,7 @@ Works with **Claude Desktop, Cursor, Windsurf** natively:
|
|
|
110
112
|
claude mcp add ompa -- python -m ompa.mcp_server
|
|
111
113
|
```
|
|
112
114
|
|
|
113
|
-
Tools: `ao_session_start`, `ao_classify`, `ao_search`, `ao_kg_query`, `ao_kg_add`, `
|
|
115
|
+
Tools: `ao_session_start`, `ao_classify`, `ao_search`, `ao_kg_query`, `ao_kg_add`, `ao_palace_wings`, `ao_palace_rooms`, `ao_palace_tunnel`, `ao_validate`, `ao_wrap_up`, `ao_status`, `ao_orphans`, `ao_init`, `ao_search`, `ao_stop`
|
|
114
116
|
|
|
115
117
|
## Python API
|
|
116
118
|
|
|
@@ -163,9 +165,8 @@ Unlike MemPalace (Claude Code + MCP only) or obsidian-mind (Claude Code hooks on
|
|
|
163
165
|
## Installation
|
|
164
166
|
|
|
165
167
|
```bash
|
|
166
|
-
pip install ompa
|
|
167
|
-
pip install ompa[
|
|
168
|
-
pip install ompa[all] # All optional dependencies
|
|
168
|
+
pip install ompa # Core only
|
|
169
|
+
pip install ompa[all] # All dependencies including sentence-transformers
|
|
169
170
|
```
|
|
170
171
|
|
|
171
172
|
Requires Python 3.10+.
|
|
@@ -177,17 +178,17 @@ Because memory should not be coupled to your agent framework. Build once, use an
|
|
|
177
178
|
## Comparison
|
|
178
179
|
|
|
179
180
|
| Feature | OMPA | MemPalace | obsidian-mind |
|
|
180
|
-
|
|
181
|
+
|---------|-----------------|------------|---------------|
|
|
181
182
|
| Framework | Any | Claude Code | Claude Code |
|
|
182
183
|
| Memory type | Vault + Palace + KG | Palace + KG | Vault only |
|
|
183
184
|
| Semantic search | Local (free) | ChromaDB API | QMD (paid) |
|
|
184
|
-
| Temporal KG | SQLite | SQLite |
|
|
185
|
-
| MCP server |
|
|
186
|
-
| CLI |
|
|
185
|
+
| Temporal KG | SQLite ✓ | SQLite ✓ | ✗ |
|
|
186
|
+
| MCP server | 15 tools | 15 tools | ✗ |
|
|
187
|
+
| CLI | 14 commands | ✗ | ✗ |
|
|
187
188
|
| Hooks | 5 lifecycle | 3 lifecycle | 3 lifecycle |
|
|
188
189
|
| Message types | 15 | 15 | 5 |
|
|
189
|
-
| Verbatim storage |
|
|
190
|
-
| Multi-agent |
|
|
190
|
+
| Verbatim storage | ✓ | ✓ | ✗ |
|
|
191
|
+
| Multi-agent | ✓ | ✗ | ✗ |
|
|
191
192
|
|
|
192
193
|
## License
|
|
193
194
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Message classification for routing and context injection.
|
|
3
3
|
Classifies user messages into categories and injects routing hints.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import re
|
|
6
7
|
from enum import Enum
|
|
7
8
|
from dataclasses import dataclass
|
|
@@ -39,7 +40,7 @@ class MessageClassifier:
|
|
|
39
40
|
Classifies user messages and provides routing guidance.
|
|
40
41
|
Inspired by obsidian-minds classify-message.py but framework-agnostic.
|
|
41
42
|
"""
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
# Regex patterns for classification
|
|
44
45
|
PATTERNS = {
|
|
45
46
|
MessageType.DECISION: [
|
|
@@ -109,7 +110,7 @@ class MessageClassifier:
|
|
|
109
110
|
r"\b(start session|start work|starting)\b",
|
|
110
111
|
],
|
|
111
112
|
}
|
|
112
|
-
|
|
113
|
+
|
|
113
114
|
# Routing hints per message type
|
|
114
115
|
ROUTING_HINTS = {
|
|
115
116
|
MessageType.DECISION: [
|
|
@@ -173,7 +174,7 @@ class MessageClassifier:
|
|
|
173
174
|
"Read brain/North Star.md first",
|
|
174
175
|
],
|
|
175
176
|
}
|
|
176
|
-
|
|
177
|
+
|
|
177
178
|
# Suggested folder locations
|
|
178
179
|
FOLDER_MAP = {
|
|
179
180
|
MessageType.DECISION: "work/active/",
|
|
@@ -191,20 +192,20 @@ class MessageClassifier:
|
|
|
191
192
|
MessageType.WRAP_UP: "brain/",
|
|
192
193
|
MessageType.STANDUP: "brain/",
|
|
193
194
|
}
|
|
194
|
-
|
|
195
|
+
|
|
195
196
|
def classify(self, message: str) -> Classification:
|
|
196
197
|
"""
|
|
197
198
|
Classify a user message and return routing guidance.
|
|
198
|
-
|
|
199
|
+
|
|
199
200
|
Args:
|
|
200
201
|
message: The user message text
|
|
201
|
-
|
|
202
|
+
|
|
202
203
|
Returns:
|
|
203
204
|
Classification with type, confidence, hints, and suggested actions
|
|
204
205
|
"""
|
|
205
206
|
message_lower = message.lower()
|
|
206
207
|
scores = {}
|
|
207
|
-
|
|
208
|
+
|
|
208
209
|
for msg_type, patterns in self.PATTERNS.items():
|
|
209
210
|
score = 0
|
|
210
211
|
for pattern in patterns:
|
|
@@ -212,32 +213,32 @@ class MessageClassifier:
|
|
|
212
213
|
score += 1
|
|
213
214
|
if score > 0:
|
|
214
215
|
scores[msg_type] = score
|
|
215
|
-
|
|
216
|
+
|
|
216
217
|
if not scores:
|
|
217
218
|
return Classification(
|
|
218
219
|
message_type=MessageType.UNKNOWN,
|
|
219
220
|
confidence=0.0,
|
|
220
221
|
routing_hints=["Process as normal conversation"],
|
|
221
222
|
suggested_folder="thinking/",
|
|
222
|
-
suggested_action="Continue conversation normally"
|
|
223
|
+
suggested_action="Continue conversation normally",
|
|
223
224
|
)
|
|
224
|
-
|
|
225
|
+
|
|
225
226
|
# Get highest scoring type
|
|
226
227
|
best_type = max(scores, key=scores.get)
|
|
227
228
|
confidence = min(scores[best_type] / 3.0, 1.0) # Normalize to 0-1
|
|
228
|
-
|
|
229
|
+
|
|
229
230
|
# For short messages, reduce confidence
|
|
230
231
|
if len(message.split()) < 5:
|
|
231
232
|
confidence *= 0.7
|
|
232
|
-
|
|
233
|
+
|
|
233
234
|
return Classification(
|
|
234
235
|
message_type=best_type,
|
|
235
236
|
confidence=confidence,
|
|
236
237
|
routing_hints=self.ROUTING_HINTS.get(best_type, []),
|
|
237
238
|
suggested_folder=self.FOLDER_MAP.get(best_type, "thinking/"),
|
|
238
|
-
suggested_action=self._get_action(best_type)
|
|
239
|
+
suggested_action=self._get_action(best_type),
|
|
239
240
|
)
|
|
240
|
-
|
|
241
|
+
|
|
241
242
|
def _get_action(self, msg_type: MessageType) -> str:
|
|
242
243
|
"""Get the primary action for a message type."""
|
|
243
244
|
actions = {
|
|
@@ -258,7 +259,7 @@ class MessageClassifier:
|
|
|
258
259
|
MessageType.UNKNOWN: "Continue conversation",
|
|
259
260
|
}
|
|
260
261
|
return actions.get(msg_type, "Continue conversation")
|
|
261
|
-
|
|
262
|
+
|
|
262
263
|
def get_routing_hint(self, message: str) -> str:
|
|
263
264
|
"""
|
|
264
265
|
Get a single-line routing hint for a message.
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
CLI for OMPA.
|
|
3
3
|
Run with: ao <command> or ao-mcp <command>
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
import typer
|
|
8
9
|
from rich.console import Console
|
|
9
10
|
from rich.table import Table
|
|
10
11
|
|
|
11
|
-
from ompa import Ompa
|
|
12
|
+
from ompa import Ompa
|
|
12
13
|
|
|
13
14
|
app = typer.Typer(help="OMPA — Universal AI agent memory layer")
|
|
14
15
|
console = Console()
|
|
@@ -20,6 +21,7 @@ def init(
|
|
|
20
21
|
):
|
|
21
22
|
"""Initialize vault + palace structure."""
|
|
22
23
|
from ompa import Vault
|
|
24
|
+
|
|
23
25
|
vault = Vault(vault_path)
|
|
24
26
|
stats = vault.get_stats()
|
|
25
27
|
ao = Ompa(vault_path, enable_semantic=False)
|
|
@@ -45,16 +47,12 @@ def status(
|
|
|
45
47
|
console.print(f" Total notes: {vault_stats['total_notes']}")
|
|
46
48
|
console.print(f" Brain notes: {vault_stats['brain_notes']}")
|
|
47
49
|
console.print(f" Orphans: {vault_stats['orphans']}")
|
|
48
|
-
|
|
49
50
|
console.print("[bold]Palace[/bold]")
|
|
50
51
|
console.print(f" Wings: {palace_stats['wing_count']}")
|
|
51
52
|
console.print(f" Rooms: {palace_stats['room_count']}")
|
|
52
53
|
console.print(f" Drawers: {palace_stats['drawer_count']}")
|
|
53
|
-
console.print(f" Tunnels: {palace_stats['tunnel_count']}")
|
|
54
|
-
|
|
55
54
|
console.print("[bold]Knowledge Graph[/bold]")
|
|
56
55
|
console.print(f" Entities: {kg_stats['entity_count']}")
|
|
57
|
-
console.print(f" Triples: {kg_stats['triple_count']}")
|
|
58
56
|
console.print(f" Current facts: {kg_stats['current_facts']}")
|
|
59
57
|
|
|
60
58
|
|
|
@@ -102,7 +100,11 @@ def search(
|
|
|
102
100
|
table.add_column("Excerpt")
|
|
103
101
|
|
|
104
102
|
for r in results:
|
|
105
|
-
excerpt =
|
|
103
|
+
excerpt = (
|
|
104
|
+
r.content_excerpt[:80] + "..."
|
|
105
|
+
if len(r.content_excerpt) > 80
|
|
106
|
+
else r.content_excerpt
|
|
107
|
+
)
|
|
106
108
|
table.add_row(f"{r.score:.2f}", r.match_type, r.path, excerpt)
|
|
107
109
|
|
|
108
110
|
console.print(table)
|
|
@@ -120,7 +122,11 @@ def orphans(
|
|
|
120
122
|
else:
|
|
121
123
|
console.print(f"[yellow]Found {len(orphan_notes)} orphan notes:[/yellow]")
|
|
122
124
|
for note in orphan_notes:
|
|
123
|
-
rel =
|
|
125
|
+
rel = (
|
|
126
|
+
note.path.relative_to(vault_path)
|
|
127
|
+
if note.path.is_relative_to(vault_path)
|
|
128
|
+
else note.path
|
|
129
|
+
)
|
|
124
130
|
console.print(f" - {rel}")
|
|
125
131
|
|
|
126
132
|
|
|
@@ -184,7 +190,9 @@ def tunnel(
|
|
|
184
190
|
else:
|
|
185
191
|
console.print(f"[bold]Tunnels between {wing_a} and {wing_b}:[/bold]")
|
|
186
192
|
for t in tunnels:
|
|
187
|
-
console.print(
|
|
193
|
+
console.print(
|
|
194
|
+
f" - {t['wing_a']}/{t['room']} <-> {t['wing_b']}/{t['room']}"
|
|
195
|
+
)
|
|
188
196
|
|
|
189
197
|
|
|
190
198
|
@app.command()
|
|
@@ -205,10 +213,7 @@ def kg_query(
|
|
|
205
213
|
if as_of:
|
|
206
214
|
console.print(f" (as of {as_of})")
|
|
207
215
|
for t in triples:
|
|
208
|
-
|
|
209
|
-
validity += f" to {t.valid_to}" if t.valid_to else ""
|
|
210
|
-
validity += ")"
|
|
211
|
-
console.print(f" {t.subject} --{t.predicate}--> {t.object} {validity}")
|
|
216
|
+
console.print(f" {t.subject} --{t.predicate}--> {t.object}")
|
|
212
217
|
|
|
213
218
|
|
|
214
219
|
@app.command()
|
|
@@ -226,7 +231,7 @@ def kg_timeline(
|
|
|
226
231
|
|
|
227
232
|
console.print(f"[bold]Timeline: {entity}[/bold]")
|
|
228
233
|
for event in timeline:
|
|
229
|
-
date_str = event
|
|
234
|
+
date_str = event.get("date", "unknown")
|
|
230
235
|
console.print(f" [{date_str}] {event['label']}")
|
|
231
236
|
|
|
232
237
|
|
|
@@ -249,6 +254,7 @@ def validate(
|
|
|
249
254
|
"""Validate all notes in the vault."""
|
|
250
255
|
ao = Ompa(vault_path, enable_semantic=False)
|
|
251
256
|
from ompa import Vault
|
|
257
|
+
|
|
252
258
|
vault = Vault(vault_path)
|
|
253
259
|
notes = vault.list_notes()
|
|
254
260
|
|
|
@@ -259,15 +265,17 @@ def validate(
|
|
|
259
265
|
if result["warnings"]:
|
|
260
266
|
total += 1
|
|
261
267
|
for w in result["warnings"]:
|
|
262
|
-
rel =
|
|
268
|
+
rel = (
|
|
269
|
+
note.path.relative_to(vault_path)
|
|
270
|
+
if note.path.is_relative_to(vault_path)
|
|
271
|
+
else note.path
|
|
272
|
+
)
|
|
263
273
|
warnings_list.append(f" {rel}: {w}")
|
|
264
274
|
|
|
265
275
|
if warnings_list:
|
|
266
|
-
console.print(f"[yellow]Found
|
|
267
|
-
for w in warnings_list
|
|
276
|
+
console.print(f"[yellow]Found {total} notes with warnings:[/yellow]")
|
|
277
|
+
for w in warnings_list:
|
|
268
278
|
console.print(w)
|
|
269
|
-
if len(warnings_list) > 20:
|
|
270
|
-
console.print(f" ... and {len(warnings_list) - 20} more")
|
|
271
279
|
else:
|
|
272
280
|
console.print("[green]All notes valid![/green]")
|
|
273
281
|
|
|
@@ -279,11 +287,10 @@ def rebuild_index(
|
|
|
279
287
|
"""Rebuild the semantic search index."""
|
|
280
288
|
ao = Ompa(vault_path, enable_semantic=True)
|
|
281
289
|
count = ao.rebuild_index()
|
|
282
|
-
console.print(f"[green]
|
|
290
|
+
console.print(f"[green]Rebuilt index: {count} files indexed[/green]")
|
|
283
291
|
|
|
284
292
|
|
|
285
293
|
def main():
|
|
286
|
-
"""Main entry point."""
|
|
287
294
|
app()
|
|
288
295
|
|
|
289
296
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
OMPA — Universal AI Agent Memory Layer
|
|
3
3
|
Core module integrating vault, palace, KG, hooks, classifier, and semantic search.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Optional
|
|
7
8
|
|
|
@@ -9,7 +10,7 @@ from .vault import Vault
|
|
|
9
10
|
from .palace import Palace
|
|
10
11
|
from .knowledge_graph import KnowledgeGraph
|
|
11
12
|
from .hooks import HookManager, HookResult
|
|
12
|
-
from .classifier import MessageClassifier, Classification
|
|
13
|
+
from .classifier import MessageClassifier, Classification
|
|
13
14
|
from .semantic import SemanticIndex, SearchResult
|
|
14
15
|
|
|
15
16
|
|
|
@@ -45,7 +46,9 @@ class Ompa:
|
|
|
45
46
|
# Core systems
|
|
46
47
|
self.vault = Vault(self.vault_path)
|
|
47
48
|
self.palace = Palace(self.vault_path / ".palace")
|
|
48
|
-
self.kg = KnowledgeGraph(
|
|
49
|
+
self.kg = KnowledgeGraph(
|
|
50
|
+
db_path=str(self.vault_path / ".palace" / "knowledge_graph.sqlite3")
|
|
51
|
+
)
|
|
49
52
|
self.classifier = MessageClassifier()
|
|
50
53
|
self.hooks = HookManager(self.vault_path, agent_name=self.agent_name)
|
|
51
54
|
|
|
@@ -152,8 +155,12 @@ class Ompa:
|
|
|
152
155
|
|
|
153
156
|
self.palace.create_room(wing, room)
|
|
154
157
|
self.palace.link_drawer(wing, room, str(path))
|
|
155
|
-
except Exception:
|
|
156
|
-
|
|
158
|
+
except Exception as e:
|
|
159
|
+
import logging
|
|
160
|
+
|
|
161
|
+
logging.getLogger(__name__).debug(
|
|
162
|
+
"Palace auto-add failed for %s: %s", file_path, e
|
|
163
|
+
)
|
|
157
164
|
|
|
158
165
|
# -------------------------------------------------------------------------
|
|
159
166
|
# Classification
|
|
@@ -286,7 +293,9 @@ class Ompa:
|
|
|
286
293
|
source: str = None,
|
|
287
294
|
) -> None:
|
|
288
295
|
"""Add a fact to the knowledge graph."""
|
|
289
|
-
self.kg.add_triple(
|
|
296
|
+
self.kg.add_triple(
|
|
297
|
+
subject, predicate, object, valid_from=valid_from, source=source
|
|
298
|
+
)
|
|
290
299
|
|
|
291
300
|
def kg_query(self, entity: str, as_of: str = None) -> list:
|
|
292
301
|
"""Query the knowledge graph."""
|