friday-memory 1.2.0__py3-none-any.whl
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.
- friday/__init__.py +32 -0
- friday/client.py +298 -0
- friday/exceptions.py +34 -0
- friday/integrations/__init__.py +1 -0
- friday/integrations/langchain.py +79 -0
- friday/types.py +41 -0
- friday_memory-1.2.0.dist-info/METADATA +574 -0
- friday_memory-1.2.0.dist-info/RECORD +10 -0
- friday_memory-1.2.0.dist-info/WHEEL +4 -0
- friday_memory-1.2.0.dist-info/licenses/LICENSE +21 -0
friday/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Friday — Persistent Cognitive Memory Layer for AI Coding Agents.
|
|
2
|
+
|
|
3
|
+
Official Python SDK for self-hosted Friday cognitive memory server.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .client import AsyncFriday, Friday
|
|
7
|
+
from .exceptions import (
|
|
8
|
+
FridayAPIError,
|
|
9
|
+
FridayAuthenticationError,
|
|
10
|
+
FridayConnectionError,
|
|
11
|
+
FridayError,
|
|
12
|
+
FridayNotFoundError,
|
|
13
|
+
)
|
|
14
|
+
from .types import Fact, GraphData, HealthStatus, MemoryResult, SearchResult
|
|
15
|
+
|
|
16
|
+
__version__ = "1.2.0"
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Friday",
|
|
20
|
+
"AsyncFriday",
|
|
21
|
+
"FridayError",
|
|
22
|
+
"FridayConnectionError",
|
|
23
|
+
"FridayAuthenticationError",
|
|
24
|
+
"FridayNotFoundError",
|
|
25
|
+
"FridayAPIError",
|
|
26
|
+
"Fact",
|
|
27
|
+
"MemoryResult",
|
|
28
|
+
"SearchResult",
|
|
29
|
+
"HealthStatus",
|
|
30
|
+
"GraphData",
|
|
31
|
+
"__version__",
|
|
32
|
+
]
|
friday/client.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Friday Python SDK — Core Client Implementation.
|
|
2
|
+
|
|
3
|
+
Provides synchronous (`Friday`) and asynchronous (`AsyncFriday`) clients
|
|
4
|
+
for interacting with the self-hosted Friday cognitive memory substrate.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
FridayAPIError,
|
|
14
|
+
FridayAuthenticationError,
|
|
15
|
+
FridayConnectionError,
|
|
16
|
+
FridayNotFoundError,
|
|
17
|
+
)
|
|
18
|
+
from .types import Fact, GraphData, HealthStatus, MemoryResult
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _resolve_config(
|
|
22
|
+
api_key: Optional[str] = None,
|
|
23
|
+
base_url: Optional[str] = None,
|
|
24
|
+
) -> tuple[str, str]:
|
|
25
|
+
resolved_url = (
|
|
26
|
+
base_url
|
|
27
|
+
or os.environ.get("FRIDAY_URL")
|
|
28
|
+
or os.environ.get("BRAIN_URL")
|
|
29
|
+
or "http://localhost:8000"
|
|
30
|
+
).rstrip("/")
|
|
31
|
+
resolved_key = (
|
|
32
|
+
api_key or os.environ.get("FRIDAY_API_KEY") or os.environ.get("BRAIN_API_KEY") or ""
|
|
33
|
+
)
|
|
34
|
+
return resolved_url, resolved_key
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _handle_response_error(response: httpx.Response) -> None:
|
|
38
|
+
if response.is_success:
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if response.status_code == 401:
|
|
42
|
+
raise FridayAuthenticationError("Invalid or missing API key.")
|
|
43
|
+
elif response.status_code == 404:
|
|
44
|
+
raise FridayNotFoundError(f"Resource not found: {response.url}")
|
|
45
|
+
else:
|
|
46
|
+
raise FridayAPIError(
|
|
47
|
+
message=f"HTTP {response.status_code}",
|
|
48
|
+
status_code=response.status_code,
|
|
49
|
+
response_text=response.text,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Friday:
|
|
54
|
+
"""Synchronous Friday Cognitive Memory Client."""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
api_key: Optional[str] = None,
|
|
59
|
+
base_url: Optional[str] = None,
|
|
60
|
+
timeout: float = 30.0,
|
|
61
|
+
transport: Optional[httpx.BaseTransport] = None,
|
|
62
|
+
):
|
|
63
|
+
self.base_url, self.api_key = _resolve_config(api_key, base_url)
|
|
64
|
+
self.headers = {
|
|
65
|
+
"Content-Type": "application/json",
|
|
66
|
+
"X-Brain-Key": self.api_key,
|
|
67
|
+
}
|
|
68
|
+
self.client = httpx.Client(
|
|
69
|
+
base_url=self.base_url,
|
|
70
|
+
headers=self.headers,
|
|
71
|
+
timeout=timeout,
|
|
72
|
+
transport=transport,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def __enter__(self) -> "Friday":
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
79
|
+
self.close()
|
|
80
|
+
|
|
81
|
+
def close(self) -> None:
|
|
82
|
+
"""Close the underlying HTTP client."""
|
|
83
|
+
self.client.close()
|
|
84
|
+
|
|
85
|
+
def health(self) -> HealthStatus:
|
|
86
|
+
"""Check the health status of all Friday memory layers."""
|
|
87
|
+
try:
|
|
88
|
+
r = self.client.get("/health")
|
|
89
|
+
_handle_response_error(r)
|
|
90
|
+
return r.json()
|
|
91
|
+
except httpx.ConnectError as e:
|
|
92
|
+
raise FridayConnectionError(
|
|
93
|
+
f"Could not connect to Friday at {self.base_url}: {e}"
|
|
94
|
+
) from e
|
|
95
|
+
except httpx.TimeoutException as e:
|
|
96
|
+
raise FridayConnectionError(
|
|
97
|
+
f"Connection timed out connecting to Friday at {self.base_url}: {e}"
|
|
98
|
+
) from e
|
|
99
|
+
|
|
100
|
+
def add_memory(self, content: str, project: str = "default") -> MemoryResult:
|
|
101
|
+
"""Store an architectural decision or context chunk into episodic memory."""
|
|
102
|
+
try:
|
|
103
|
+
payload = {"content": content, "project": project}
|
|
104
|
+
r = self.client.post("/add", json=payload)
|
|
105
|
+
_handle_response_error(r)
|
|
106
|
+
return r.json()
|
|
107
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
108
|
+
raise FridayConnectionError(f"Network error adding memory: {e}") from e
|
|
109
|
+
|
|
110
|
+
def search(self, query: str, project: Optional[str] = None) -> Dict[str, Any]:
|
|
111
|
+
"""Search memory layers semantically for relevant architectural context."""
|
|
112
|
+
try:
|
|
113
|
+
payload: Dict[str, Any] = {"query": query}
|
|
114
|
+
if project:
|
|
115
|
+
payload["project"] = project
|
|
116
|
+
r = self.client.post("/search", json=payload)
|
|
117
|
+
_handle_response_error(r)
|
|
118
|
+
return r.json()
|
|
119
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
120
|
+
raise FridayConnectionError(f"Network error searching memory: {e}") from e
|
|
121
|
+
|
|
122
|
+
def add_fact(self, content: str) -> Dict[str, Any]:
|
|
123
|
+
"""Commit an atomic, versioned ground-truth fact to the facts ledger."""
|
|
124
|
+
try:
|
|
125
|
+
payload = {"content": content}
|
|
126
|
+
r = self.client.post("/facts", json=payload)
|
|
127
|
+
_handle_response_error(r)
|
|
128
|
+
return r.json()
|
|
129
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
130
|
+
raise FridayConnectionError(f"Network error adding fact: {e}") from e
|
|
131
|
+
|
|
132
|
+
def get_facts(self, include_superseded: bool = False) -> List[Fact]:
|
|
133
|
+
"""Retrieve verified ground-truth facts from the ledger."""
|
|
134
|
+
try:
|
|
135
|
+
url = "/facts?include_superseded=true" if include_superseded else "/facts"
|
|
136
|
+
r = self.client.get(url)
|
|
137
|
+
_handle_response_error(r)
|
|
138
|
+
data = r.json()
|
|
139
|
+
return data.get("facts", [])
|
|
140
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
141
|
+
raise FridayConnectionError(f"Network error retrieving facts: {e}") from e
|
|
142
|
+
|
|
143
|
+
def get_context(self, query: Optional[str] = None, target: str = "agents") -> str:
|
|
144
|
+
"""Export compiled architectural directives and verified facts for prompt injection."""
|
|
145
|
+
try:
|
|
146
|
+
url = f"/export/persona?target={target}"
|
|
147
|
+
r = self.client.get(url)
|
|
148
|
+
_handle_response_error(r)
|
|
149
|
+
return r.text
|
|
150
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
151
|
+
raise FridayConnectionError(f"Network error retrieving context: {e}") from e
|
|
152
|
+
|
|
153
|
+
def get_graph_data(self) -> GraphData:
|
|
154
|
+
"""Fetch all entities and typed relationships from the Neo4j knowledge graph."""
|
|
155
|
+
try:
|
|
156
|
+
r = self.client.get("/api/graph-data")
|
|
157
|
+
_handle_response_error(r)
|
|
158
|
+
return r.json()
|
|
159
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
160
|
+
raise FridayConnectionError(f"Network error retrieving graph data: {e}") from e
|
|
161
|
+
|
|
162
|
+
def ingest(
|
|
163
|
+
self, text: str, filename: Optional[str] = None, source: str = "document"
|
|
164
|
+
) -> Dict[str, Any]:
|
|
165
|
+
"""Batch ingest architectural specifications or documentation into the knowledge base."""
|
|
166
|
+
try:
|
|
167
|
+
payload = {"text": text, "source": source}
|
|
168
|
+
if filename:
|
|
169
|
+
payload["filename"] = filename
|
|
170
|
+
r = self.client.post("/ingest", json=payload)
|
|
171
|
+
_handle_response_error(r)
|
|
172
|
+
return r.json()
|
|
173
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
174
|
+
raise FridayConnectionError(f"Network error ingesting document: {e}") from e
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class AsyncFriday:
|
|
178
|
+
"""Asynchronous Friday Cognitive Memory Client."""
|
|
179
|
+
|
|
180
|
+
def __init__(
|
|
181
|
+
self,
|
|
182
|
+
api_key: Optional[str] = None,
|
|
183
|
+
base_url: Optional[str] = None,
|
|
184
|
+
timeout: float = 30.0,
|
|
185
|
+
transport: Optional[httpx.AsyncBaseTransport] = None,
|
|
186
|
+
):
|
|
187
|
+
self.base_url, self.api_key = _resolve_config(api_key, base_url)
|
|
188
|
+
self.headers = {
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
"X-Brain-Key": self.api_key,
|
|
191
|
+
}
|
|
192
|
+
self.client = httpx.AsyncClient(
|
|
193
|
+
base_url=self.base_url,
|
|
194
|
+
headers=self.headers,
|
|
195
|
+
timeout=timeout,
|
|
196
|
+
transport=transport,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
async def __aenter__(self) -> "AsyncFriday":
|
|
200
|
+
return self
|
|
201
|
+
|
|
202
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
203
|
+
await self.close()
|
|
204
|
+
|
|
205
|
+
async def close(self) -> None:
|
|
206
|
+
"""Close the underlying HTTP client."""
|
|
207
|
+
await self.client.aclose()
|
|
208
|
+
|
|
209
|
+
async def health(self) -> HealthStatus:
|
|
210
|
+
"""Check the health status of all Friday memory layers."""
|
|
211
|
+
try:
|
|
212
|
+
r = await self.client.get("/health")
|
|
213
|
+
_handle_response_error(r)
|
|
214
|
+
return r.json()
|
|
215
|
+
except httpx.ConnectError as e:
|
|
216
|
+
raise FridayConnectionError(
|
|
217
|
+
f"Could not connect to Friday at {self.base_url}: {e}"
|
|
218
|
+
) from e
|
|
219
|
+
except httpx.TimeoutException as e:
|
|
220
|
+
raise FridayConnectionError(
|
|
221
|
+
f"Connection timed out connecting to Friday at {self.base_url}: {e}"
|
|
222
|
+
) from e
|
|
223
|
+
|
|
224
|
+
async def add_memory(self, content: str, project: str = "default") -> MemoryResult:
|
|
225
|
+
"""Store an architectural decision or context chunk into episodic memory."""
|
|
226
|
+
try:
|
|
227
|
+
payload = {"content": content, "project": project}
|
|
228
|
+
r = await self.client.post("/add", json=payload)
|
|
229
|
+
_handle_response_error(r)
|
|
230
|
+
return r.json()
|
|
231
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
232
|
+
raise FridayConnectionError(f"Network error adding memory: {e}") from e
|
|
233
|
+
|
|
234
|
+
async def search(self, query: str, project: Optional[str] = None) -> Dict[str, Any]:
|
|
235
|
+
"""Search memory layers semantically for relevant architectural context."""
|
|
236
|
+
try:
|
|
237
|
+
payload: Dict[str, Any] = {"query": query}
|
|
238
|
+
if project:
|
|
239
|
+
payload["project"] = project
|
|
240
|
+
r = await self.client.post("/search", json=payload)
|
|
241
|
+
_handle_response_error(r)
|
|
242
|
+
return r.json()
|
|
243
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
244
|
+
raise FridayConnectionError(f"Network error searching memory: {e}") from e
|
|
245
|
+
|
|
246
|
+
async def add_fact(self, content: str) -> Dict[str, Any]:
|
|
247
|
+
"""Commit an atomic, versioned ground-truth fact to the facts ledger."""
|
|
248
|
+
try:
|
|
249
|
+
payload = {"content": content}
|
|
250
|
+
r = await self.client.post("/facts", json=payload)
|
|
251
|
+
_handle_response_error(r)
|
|
252
|
+
return r.json()
|
|
253
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
254
|
+
raise FridayConnectionError(f"Network error adding fact: {e}") from e
|
|
255
|
+
|
|
256
|
+
async def get_facts(self, include_superseded: bool = False) -> List[Fact]:
|
|
257
|
+
"""Retrieve verified ground-truth facts from the ledger."""
|
|
258
|
+
try:
|
|
259
|
+
url = "/facts?include_superseded=true" if include_superseded else "/facts"
|
|
260
|
+
r = await self.client.get(url)
|
|
261
|
+
_handle_response_error(r)
|
|
262
|
+
data = r.json()
|
|
263
|
+
return data.get("facts", [])
|
|
264
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
265
|
+
raise FridayConnectionError(f"Network error retrieving facts: {e}") from e
|
|
266
|
+
|
|
267
|
+
async def get_context(self, query: Optional[str] = None, target: str = "agents") -> str:
|
|
268
|
+
"""Export compiled architectural directives and verified facts for prompt injection."""
|
|
269
|
+
try:
|
|
270
|
+
url = f"/export/persona?target={target}"
|
|
271
|
+
r = await self.client.get(url)
|
|
272
|
+
_handle_response_error(r)
|
|
273
|
+
return r.text
|
|
274
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
275
|
+
raise FridayConnectionError(f"Network error retrieving context: {e}") from e
|
|
276
|
+
|
|
277
|
+
async def get_graph_data(self) -> GraphData:
|
|
278
|
+
"""Fetch all entities and typed relationships from the Neo4j knowledge graph."""
|
|
279
|
+
try:
|
|
280
|
+
r = await self.client.get("/api/graph-data")
|
|
281
|
+
_handle_response_error(r)
|
|
282
|
+
return r.json()
|
|
283
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
284
|
+
raise FridayConnectionError(f"Network error retrieving graph data: {e}") from e
|
|
285
|
+
|
|
286
|
+
async def ingest(
|
|
287
|
+
self, text: str, filename: Optional[str] = None, source: str = "document"
|
|
288
|
+
) -> Dict[str, Any]:
|
|
289
|
+
"""Batch ingest architectural specifications or documentation into the knowledge base."""
|
|
290
|
+
try:
|
|
291
|
+
payload = {"text": text, "source": source}
|
|
292
|
+
if filename:
|
|
293
|
+
payload["filename"] = filename
|
|
294
|
+
r = await self.client.post("/ingest", json=payload)
|
|
295
|
+
_handle_response_error(r)
|
|
296
|
+
return r.json()
|
|
297
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
298
|
+
raise FridayConnectionError(f"Network error ingesting document: {e}") from e
|
friday/exceptions.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Friday Python SDK — Custom Exceptions."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FridayError(Exception):
|
|
5
|
+
"""Base exception for all Friday SDK errors."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FridayConnectionError(FridayError):
|
|
11
|
+
"""Raised when the client fails to connect to the Friday server or times out."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FridayAuthenticationError(FridayError):
|
|
17
|
+
"""Raised when the API key is missing, invalid, or unauthorized (HTTP 401)."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FridayNotFoundError(FridayError):
|
|
23
|
+
"""Raised when a requested resource is not found (HTTP 404)."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class FridayAPIError(FridayError):
|
|
29
|
+
"""Raised when the Friday API returns an unexpected error response."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, message: str, status_code: int = 500, response_text: str = ""):
|
|
32
|
+
super().__init__(f"[{status_code}] {message}: {response_text}".strip())
|
|
33
|
+
self.status_code = status_code
|
|
34
|
+
self.response_text = response_text
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Friday Python SDK — Framework Integrations."""
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Friday LangChain Integration — Custom BaseRetriever."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, List, Optional
|
|
4
|
+
|
|
5
|
+
from ..client import Friday
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from langchain_core.callbacks import CallbackManagerForRetrieverRun
|
|
9
|
+
from langchain_core.documents import Document
|
|
10
|
+
from langchain_core.retrievers import BaseRetriever
|
|
11
|
+
|
|
12
|
+
_HAS_LANGCHAIN = True
|
|
13
|
+
except ImportError:
|
|
14
|
+
_HAS_LANGCHAIN = False
|
|
15
|
+
BaseRetriever = None # type: ignore
|
|
16
|
+
Document = None # type: ignore
|
|
17
|
+
CallbackManagerForRetrieverRun = None # type: ignore
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if _HAS_LANGCHAIN:
|
|
21
|
+
|
|
22
|
+
class FridayRetriever(BaseRetriever): # type: ignore[misc]
|
|
23
|
+
"""LangChain Retriever for Friday Cognitive Memory Layer.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
```python
|
|
27
|
+
from friday.integrations.langchain import FridayRetriever
|
|
28
|
+
|
|
29
|
+
retriever = FridayRetriever(api_key="secret", base_url="http://localhost:8000")
|
|
30
|
+
docs = retriever.invoke("What database do we use?")
|
|
31
|
+
```
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
api_key: Optional[str] = None
|
|
35
|
+
base_url: Optional[str] = "http://localhost:8000"
|
|
36
|
+
project: str = "default"
|
|
37
|
+
|
|
38
|
+
def _get_relevant_documents(
|
|
39
|
+
self, query: str, *, run_manager: Optional[CallbackManagerForRetrieverRun] = None
|
|
40
|
+
) -> List[Document]:
|
|
41
|
+
with Friday(api_key=self.api_key, base_url=self.base_url) as client:
|
|
42
|
+
res = client.search(query=query, project=self.project)
|
|
43
|
+
docs = []
|
|
44
|
+
results = res.get("results", {})
|
|
45
|
+
|
|
46
|
+
# Parse facts
|
|
47
|
+
for fact in results.get("layer2_facts", []):
|
|
48
|
+
docs.append(Document(page_content=fact, metadata={"source": "friday_facts"}))
|
|
49
|
+
|
|
50
|
+
# Parse semantic chunks
|
|
51
|
+
for mem in results.get("layer3_semantic", []):
|
|
52
|
+
docs.append(Document(page_content=mem, metadata={"source": "friday_semantic"}))
|
|
53
|
+
|
|
54
|
+
return docs
|
|
55
|
+
|
|
56
|
+
else:
|
|
57
|
+
|
|
58
|
+
class FridayRetriever: # type: ignore[no-redef]
|
|
59
|
+
"""Stub FridayRetriever when langchain-core is not installed."""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
api_key: Optional[str] = None,
|
|
64
|
+
base_url: Optional[str] = "http://localhost:8000",
|
|
65
|
+
project: str = "default",
|
|
66
|
+
**kwargs: Any,
|
|
67
|
+
):
|
|
68
|
+
self.api_key = api_key
|
|
69
|
+
self.base_url = base_url
|
|
70
|
+
self.project = project
|
|
71
|
+
|
|
72
|
+
def _get_relevant_documents(self, query: str, *args: Any, **kwargs: Any) -> Any:
|
|
73
|
+
raise ImportError(
|
|
74
|
+
"langchain-core is required to use FridayRetriever. "
|
|
75
|
+
"Install it via: pip install 'friday-memory[langchain]' or pip install langchain-core"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def invoke(self, input: str, *args: Any, **kwargs: Any) -> Any:
|
|
79
|
+
return self._get_relevant_documents(input, *args, **kwargs)
|
friday/types.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Friday Python SDK — Type Definitions."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
from typing_extensions import TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Fact(TypedDict, total=False):
|
|
9
|
+
id: str
|
|
10
|
+
content: str
|
|
11
|
+
created_at: str
|
|
12
|
+
updated_at: str
|
|
13
|
+
version: int
|
|
14
|
+
status: str
|
|
15
|
+
source: str
|
|
16
|
+
replaces: Optional[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MemoryResult(TypedDict, total=False):
|
|
20
|
+
status: str
|
|
21
|
+
mem0_id: Optional[str]
|
|
22
|
+
chroma_id: Optional[str]
|
|
23
|
+
message: Optional[str]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SearchResult(TypedDict, total=False):
|
|
27
|
+
query: str
|
|
28
|
+
results: Dict[str, Any]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HealthStatus(TypedDict, total=False):
|
|
32
|
+
status: str
|
|
33
|
+
service: str
|
|
34
|
+
version: str
|
|
35
|
+
layers: Dict[str, str]
|
|
36
|
+
timestamp: float
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class GraphData(TypedDict, total=False):
|
|
40
|
+
nodes: List[Dict[str, Any]]
|
|
41
|
+
links: List[Dict[str, Any]]
|
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: friday-memory
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Official Python SDK and self-hosted persistent cognitive memory layer for AI coding agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/friday-memory/friday
|
|
6
|
+
Project-URL: Repository, https://github.com/friday-memory/friday.git
|
|
7
|
+
Project-URL: Issues, https://github.com/friday-memory/friday/issues
|
|
8
|
+
Author-email: Shobhit Singh <itskie7910@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,claude-code,cursor,knowledge-graph,mcp,memory,model-context-protocol,neo4j,persistent-memory,sdk
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: httpx>=0.27.0
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
25
|
+
Provides-Extra: langchain
|
|
26
|
+
Requires-Dist: langchain-core>=0.1.0; extra == 'langchain'
|
|
27
|
+
Provides-Extra: server
|
|
28
|
+
Requires-Dist: chromadb>=0.5.7; extra == 'server'
|
|
29
|
+
Requires-Dist: fastapi>=0.115.0; extra == 'server'
|
|
30
|
+
Requires-Dist: mem0ai>=0.1.29; extra == 'server'
|
|
31
|
+
Requires-Dist: neo4j>=5.24.0; extra == 'server'
|
|
32
|
+
Requires-Dist: openai>=1.45.0; extra == 'server'
|
|
33
|
+
Requires-Dist: pydantic>=2.9.2; extra == 'server'
|
|
34
|
+
Requires-Dist: python-dotenv>=1.0.1; extra == 'server'
|
|
35
|
+
Requires-Dist: sentence-transformers>=3.1.0; extra == 'server'
|
|
36
|
+
Requires-Dist: uvicorn[standard]>=0.30.6; extra == 'server'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
<div align="center">
|
|
40
|
+
|
|
41
|
+
<br>
|
|
42
|
+
|
|
43
|
+
<img src="docs/assets/banner.png" alt="Friday - Persistent Cognitive Memory Layer for AI Coding Agents" width="100%" style="border-radius: 8px; border: 1px solid #1e293b;" />
|
|
44
|
+
|
|
45
|
+
<br><br>
|
|
46
|
+
|
|
47
|
+
# Friday
|
|
48
|
+
|
|
49
|
+
**Self-hosted persistent cognitive memory layer for AI coding agents.**
|
|
50
|
+
|
|
51
|
+
Persists architecture decisions, schemas, and constraints across sessions via the Model Context Protocol (MCP).
|
|
52
|
+
|
|
53
|
+
<br>
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<a href="https://github.com/friday-memory/friday/stargazers"><img src="https://img.shields.io/github/stars/friday-memory/friday?style=flat&color=334155&label=Stars" alt="GitHub Stars"/></a>
|
|
57
|
+
<a href="https://github.com/friday-memory/friday/releases"><img src="https://img.shields.io/badge/release-v1.2.0-334155?style=flat" alt="Release"/></a>
|
|
58
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-334155?style=flat" alt="MIT License"/></a>
|
|
59
|
+
<a href="https://python.org"><img src="https://img.shields.io/badge/python-3.11+-334155?style=flat" alt="Python 3.11+"/></a>
|
|
60
|
+
<a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/protocol-MCP_2024--11--05-334155?style=flat" alt="MCP Protocol"/></a>
|
|
61
|
+
<a href="docker-compose.yml"><img src="https://img.shields.io/badge/docker-compose_ready-334155?style=flat" alt="Docker Compose Ready"/></a>
|
|
62
|
+
<a href="#deepeval-benchmarks"><img src="https://img.shields.io/badge/evals-deepeval_verified-334155?style=flat" alt="DeepEval Verified"/></a>
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<br>
|
|
66
|
+
|
|
67
|
+
<table>
|
|
68
|
+
<tr>
|
|
69
|
+
<td align="center"><a href="#the-problem-session-amnesia"><b>Overview</b></a></td>
|
|
70
|
+
<td align="center"><a href="#quickstart"><b>Quickstart</b></a></td>
|
|
71
|
+
<td align="center"><a href="#python-sdk-friday-memory"><b>Python SDK</b></a></td>
|
|
72
|
+
<td align="center"><a href="#client-setup-mcp"><b>Client Setup (MCP)</b></a></td>
|
|
73
|
+
<td align="center"><a href="#architecture"><b>Architecture</b></a></td>
|
|
74
|
+
<td align="center"><a href="#deepeval-benchmarks"><b>Benchmarks</b></a></td>
|
|
75
|
+
<td align="center"><a href="#api-reference"><b>API Reference</b></a></td>
|
|
76
|
+
</tr>
|
|
77
|
+
</table>
|
|
78
|
+
|
|
79
|
+
<br><br>
|
|
80
|
+
|
|
81
|
+
<img src="docs/assets/synthetic_neural_cortex.png" alt="Friday Neural Studio — Interactive Knowledge Graph" width="100%" style="border-radius: 8px; border: 1px solid #1e293b;" />
|
|
82
|
+
|
|
83
|
+
<br>
|
|
84
|
+
<sub><i>Friday Neural Studio — Real-time WebGL knowledge graph visualizer rendering service topologies, entity dependencies, and versioned facts.</i></sub>
|
|
85
|
+
|
|
86
|
+
<br><br>
|
|
87
|
+
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## The Problem: Session Amnesia
|
|
93
|
+
|
|
94
|
+
Modern AI coding agents (Cursor, Claude Code, Antigravity, VS Code) excel at isolated code generation. However, in continuous engineering workflows, developers encounter a structural limitation: **Session Amnesia**.
|
|
95
|
+
|
|
96
|
+
Current workarounds fall into two flawed patterns:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
┌─────────────────────────────────────────────────────────┐
|
|
100
|
+
│ WHY STANDARD APPROACHES BREAK DOWN │
|
|
101
|
+
└─────────────────────────────────────────────────────────┘
|
|
102
|
+
|
|
103
|
+
1. Context Windows (RAM) 2. Static Rules Files 3. Standard Vector RAG
|
|
104
|
+
┌─────────────────────────┐ ┌─────────────────────────┐ ┌─────────────────────────┐
|
|
105
|
+
│ • Ephemeral volatile │ │ • Linear token tax │ │ • Matches text phrasing,│
|
|
106
|
+
│ memory (clears on │ │ (2,500 tokens burned │ │ NOT system topology │
|
|
107
|
+
│ every new thread) │ │ on every trivial fix) │ │ • Blind to directed │
|
|
108
|
+
│ • Lost-in-the-middle │ │ • Stale rules accumu- │ │ call graphs & schema │
|
|
109
|
+
│ degradation on 50k+ │ │ late & conflict │ │ dependencies │
|
|
110
|
+
│ token prompts │ │ • Zero cross-tool sync │ │ • Hallucinates blast │
|
|
111
|
+
│ • High latency & cost │ │ (Cursor ≠ Claude CLI) │ │ radii of refactors │
|
|
112
|
+
└─────────────────────────┘ └─────────────────────────┘ └─────────────────────────┘
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
1. **Context Windows Are Volatile**: Context windows act as working RAM, not durable storage. Clearing a thread or restarting an agent resets state. Prompt-stuffing 50k+ tokens introduces the *"lost-in-the-middle"* attention drop and escalates inference latency.
|
|
116
|
+
2. **Static Rule Files Incur a Linear Token Tax**: Maintaining large rule files (`.cursorrules`, `AGENTS.md`) forces the model to re-read thousands of lines on every keystroke, leading to contradictory instructions and cross-editor fragmentation.
|
|
117
|
+
3. **Vector Search Misses System Topology**: Embedding cosine similarity matches text phrasing, not relational dependencies. Vector search cannot traverse directed graphs:
|
|
118
|
+
$$\text{Table: accounts} \longrightarrow \text{FK: subscriptions} \longrightarrow \text{Service: BillingService} \longrightarrow \text{Worker: InvoicePoller}$$
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Architecture: Multi-Layer Cognitive Substrate
|
|
123
|
+
|
|
124
|
+
Friday runs as a self-hosted background service providing a structured, four-tier memory substrate accessed via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io):
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
┌────────────────────────────────────────────────────────────────────────────────────────┐
|
|
128
|
+
│ AI CODING CLIENTS (Cursor / Claude Code / Antigravity / VS Code) │
|
|
129
|
+
└───────────────────────────────────────────┬────────────────────────────────────────────┘
|
|
130
|
+
│
|
|
131
|
+
4 MCP Tools (stdio / HTTP)
|
|
132
|
+
├── add_memory (persist decisions & rationale)
|
|
133
|
+
├── add_fact (versioned immutable truths)
|
|
134
|
+
├── memory_search (targeted semantic recall)
|
|
135
|
+
└── get_context (compiled multi-layer prompt)
|
|
136
|
+
│
|
|
137
|
+
▼
|
|
138
|
+
┌────────────────────────────────────────────────────────────────────────────────────────┐
|
|
139
|
+
│ FRIDAY COGNITIVE ENGINE │
|
|
140
|
+
│ │
|
|
141
|
+
│ Layer 1: Facts Ledger Layer 2: Episodic Memory Layer 3: Graph Topology │
|
|
142
|
+
│ ┌─────────────────────────┐ ┌───────────────────────────┐ ┌──────────────────────┐ │
|
|
143
|
+
│ │ Versioned SQLite │ │ Mem0 + ChromaDB │ │ Neo4j Property Graph │ │
|
|
144
|
+
│ │ • Deterministic truths │ │ • Semantic decisions │ │ • Directed call-trees│ │
|
|
145
|
+
│ │ • Conflict detection │ │ • Vector similarity │ │ • Schema blast-radius│ │
|
|
146
|
+
│ │ • Zero prompt overhead │ │ • Sub-100ms retrieval │ │ • Entity dependencies│ │
|
|
147
|
+
│ └─────────────────────────┘ └───────────────────────────┘ └──────────────────────┘ │
|
|
148
|
+
│ │
|
|
149
|
+
│ • Auto-Graph Pipeline: LLM extraction wires entities into Neo4j automatically. │
|
|
150
|
+
│ • Neural Studio: WebGL-based 3D graph visualizer for human and agent state auditing. │
|
|
151
|
+
│ • Persona Synchronization: /export/persona compiles canonical rules on-demand. │
|
|
152
|
+
└────────────────────────────────────────────────────────────────────────────────────────┘
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Architectural Comparison Matrix
|
|
158
|
+
|
|
159
|
+
| Capability | Static Prompts (`.cursorrules`) | Traditional Vector RAG | Friday Cognitive Substrate |
|
|
160
|
+
| :--- | :---: | :---: | :---: |
|
|
161
|
+
| **Cross-Session Persistence** | None (resets with thread) | Text chunks only | Full architectural state & decisions |
|
|
162
|
+
| **Dependency Graph Traversal** | None | Lexical similarity only | Neo4j Directed Property Graph |
|
|
163
|
+
| **Token Efficiency** | Burns 2,000–5,000 tokens/turn | Unfiltered chunk dumps | Targeted queries (~280 tokens/turn) |
|
|
164
|
+
| **Toolchain Synchronization** | Isolated per editor config | Disconnected silos | Unified MCP across Cursor, Claude, CLI |
|
|
165
|
+
| **Conflict Resolution** | Manual file editing required | Ingests conflicting chunks | Versioned Fact Ledger with status flags |
|
|
166
|
+
| **Topology Auditing** | None | None | Neural Studio 3D interactive viewer |
|
|
167
|
+
| **Deployment Model** | Local flat files | Cloud SaaS vendor lock-in | 100% Self-Hosted Docker Compose |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## DeepEval Benchmarks
|
|
172
|
+
|
|
173
|
+
We evaluated five realistic engineering scenarios using the [DeepEval](https://deepeval.com) evaluation framework:
|
|
174
|
+
|
|
175
|
+
1. **Database Schema Blast Radius** (evaluating downstream call-graph traversal)
|
|
176
|
+
2. **Authentication Refresh Lifecycle** (evaluating versioned constraint fidelity)
|
|
177
|
+
3. **Webhook Idempotency Guarantee** (evaluating race-condition edge cases)
|
|
178
|
+
4. **Environment & Port Reservations** (evaluating static ground-truth recall)
|
|
179
|
+
5. **Multi-Agent Toolchain Consistency** (evaluating cross-tool synchronization between Cursor and Claude CLI)
|
|
180
|
+
|
|
181
|
+
| Memory Architecture | Contextual Precision | Contextual Recall | Faithfulness | Prompt Tokens / Turn | Session Retention |
|
|
182
|
+
| :--- | :---: | :---: | :---: | :---: | :---: |
|
|
183
|
+
| **Static Prompts (`.cursorrules`)** | 38.0% | 44.0% | 62.0% | 3,150 tokens | 15.0% (resets) |
|
|
184
|
+
| **Naive Vector RAG (Vector Only)** | 64.0% | 58.0% | 74.0% | 1,820 tokens | 55.0% |
|
|
185
|
+
| **Friday Cognitive Substrate** | **95.0%** | **93.0%** | **99.0%** | **280 tokens** | **100.0%** |
|
|
186
|
+
|
|
187
|
+
#### Reproducing Benchmarks Locally
|
|
188
|
+
```bash
|
|
189
|
+
python benchmarks/benchmark_deepeval.py
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Quickstart
|
|
195
|
+
|
|
196
|
+
### Option A: One-Command Installation (Recommended)
|
|
197
|
+
|
|
198
|
+
Run the automated installer to check dependencies, generate configuration keys, and boot the stack:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
curl -fsSL https://raw.githubusercontent.com/friday-memory/friday/main/install.sh | bash
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### Option B: Manual Setup via Docker Compose
|
|
207
|
+
|
|
208
|
+
**1. Clone the repository**
|
|
209
|
+
```bash
|
|
210
|
+
git clone https://github.com/friday-memory/friday.git
|
|
211
|
+
cd friday
|
|
212
|
+
cp .env.example .env
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**2. Configure environment (`.env`)**
|
|
216
|
+
```env
|
|
217
|
+
# Master API key for endpoint security
|
|
218
|
+
FRIDAY_API_KEY=choose_a_strong_password
|
|
219
|
+
|
|
220
|
+
# LLM provider for automated graph extraction (DeepSeek or Groq)
|
|
221
|
+
DEEPSEEK_API_KEY=your_api_key_here
|
|
222
|
+
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
|
223
|
+
|
|
224
|
+
# Mem0 key for vector memory
|
|
225
|
+
MEM0_API_KEY=your_mem0_key_here
|
|
226
|
+
|
|
227
|
+
# Neo4j database credentials
|
|
228
|
+
NEO4J_PASSWORD=choose_a_secure_db_password
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**3. Launch services**
|
|
232
|
+
```bash
|
|
233
|
+
make docker-up
|
|
234
|
+
# or: docker compose up -d
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Services initialized:
|
|
238
|
+
- **Friday Gateway API**: `http://localhost:80` (or `http://localhost:8000`)
|
|
239
|
+
- **Neo4j Browser**: `http://localhost:7474`
|
|
240
|
+
- **Neural Studio UI**: `http://localhost/`
|
|
241
|
+
|
|
242
|
+
**4. Verify health**
|
|
243
|
+
```bash
|
|
244
|
+
curl http://localhost/health
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**5. Persist initial context**
|
|
248
|
+
```bash
|
|
249
|
+
curl -X POST http://localhost/add \
|
|
250
|
+
-H "X-Brain-Key: your_strong_password" \
|
|
251
|
+
-H "Content-Type: application/json" \
|
|
252
|
+
-d '{
|
|
253
|
+
"content": "Authentication uses JWT access tokens (15m expiration) with httpOnly refresh cookies. Implementation in gateway/auth.py.",
|
|
254
|
+
"project": "CoreApp"
|
|
255
|
+
}'
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Python SDK (`friday-memory`)
|
|
261
|
+
|
|
262
|
+
Connect your agentic workflows, LangChain pipelines, or autonomous scripts directly to Friday with zero boilerplate:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
pip install friday-memory
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Synchronous Client
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
from friday import Friday
|
|
272
|
+
|
|
273
|
+
# Automatically resolves FRIDAY_URL and FRIDAY_API_KEY from environment
|
|
274
|
+
with Friday(api_key="your_secret_key", base_url="http://localhost:8000") as client:
|
|
275
|
+
# 1. Health check
|
|
276
|
+
status = client.health()
|
|
277
|
+
print("Friday Status:", status["status"])
|
|
278
|
+
|
|
279
|
+
# 2. Store architectural decision
|
|
280
|
+
client.add_memory(
|
|
281
|
+
"PostgreSQL 16 selected with pgvector for hybrid retrieval",
|
|
282
|
+
project="backend-api",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# 3. Commit immutable ground-truth fact
|
|
286
|
+
client.add_fact("Production database endpoint is db.internal.net:5432")
|
|
287
|
+
|
|
288
|
+
# 4. Multi-layer search (L2 Facts + L3 ChromaDB + L4 Knowledge Graph)
|
|
289
|
+
context = client.search("database connection configuration", project="backend-api")
|
|
290
|
+
print(context["results"])
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Asynchronous Client (FastAPI / Agent Workers)
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
import asyncio
|
|
297
|
+
from friday import AsyncFriday
|
|
298
|
+
|
|
299
|
+
async def main():
|
|
300
|
+
async with AsyncFriday(api_key="your_secret_key") as client:
|
|
301
|
+
# Commit context concurrently
|
|
302
|
+
await client.add_memory("Redis cluster deployed for token bucket rate limiting")
|
|
303
|
+
facts = await client.get_facts()
|
|
304
|
+
print(f"Verified facts count: {len(facts)}")
|
|
305
|
+
|
|
306
|
+
asyncio.run(main())
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### LangChain Integration (`FridayRetriever`)
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
pip install "friday-memory[langchain]"
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from friday.integrations.langchain import FridayRetriever
|
|
317
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
318
|
+
from langchain_openai import ChatOpenAI
|
|
319
|
+
|
|
320
|
+
retriever = FridayRetriever(
|
|
321
|
+
api_key="your_secret_key",
|
|
322
|
+
base_url="http://localhost:8000",
|
|
323
|
+
project="reeldm",
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Connect directly to LCEL chains
|
|
327
|
+
prompt = ChatPromptTemplate.from_template("""Answer using verified system memory:
|
|
328
|
+
{context}
|
|
329
|
+
|
|
330
|
+
Question: {question}""")
|
|
331
|
+
|
|
332
|
+
chain = {"context": retriever, "question": RunnablePassthrough()} | prompt | ChatOpenAI()
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Client Setup (MCP)
|
|
338
|
+
|
|
339
|
+
Friday provides an official Model Context Protocol (MCP) server over `stdio` or HTTP, enabling real-time context retrieval for all supported IDEs.
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
┌───────────────────────┐
|
|
343
|
+
│ Cursor (Desktop) │──┐
|
|
344
|
+
└───────────────────────┘ │
|
|
345
|
+
┌───────────────────────┐ │
|
|
346
|
+
│ Claude Code CLI │──┼── MCP Protocol (stdio transport)
|
|
347
|
+
└───────────────────────┘ │ FRIDAY_URL="http://127.0.0.1:8000"
|
|
348
|
+
┌───────────────────────┐ │ BRAIN_API_KEY="your_secret_key"
|
|
349
|
+
│ Antigravity IDE │──┤
|
|
350
|
+
└───────────────────────┘ │
|
|
351
|
+
┌───────────────────────┐ │
|
|
352
|
+
│ Windsurf / VS Code │──┘
|
|
353
|
+
└───────────────────────┘
|
|
354
|
+
▼
|
|
355
|
+
┌──────────────────────────────┐
|
|
356
|
+
│ FRIDAY CENTRAL BRAIN │
|
|
357
|
+
│ (Localhost or Remote VM) │
|
|
358
|
+
│ FastAPI + Mem0 + Neo4j │
|
|
359
|
+
└──────────────────────────────┘
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
<details>
|
|
363
|
+
<summary><b>Cursor</b></summary>
|
|
364
|
+
|
|
365
|
+
Add to `.cursor/mcp.json` in your project or globally in **Cursor Settings → MCP**:
|
|
366
|
+
|
|
367
|
+
```json
|
|
368
|
+
{
|
|
369
|
+
"mcpServers": {
|
|
370
|
+
"friday": {
|
|
371
|
+
"command": "python",
|
|
372
|
+
"args": ["-m", "mcp.server"],
|
|
373
|
+
"cwd": "/path/to/friday",
|
|
374
|
+
"env": {
|
|
375
|
+
"FRIDAY_URL": "http://localhost:8000",
|
|
376
|
+
"BRAIN_API_KEY": "your_secret_key"
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
</details>
|
|
383
|
+
|
|
384
|
+
<details>
|
|
385
|
+
<summary><b>Claude Code CLI</b></summary>
|
|
386
|
+
|
|
387
|
+
Register Friday directly via CLI:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
claude mcp add friday \
|
|
391
|
+
-e FRIDAY_URL="http://localhost:8000" \
|
|
392
|
+
-e BRAIN_API_KEY="your_secret_key" \
|
|
393
|
+
-- python -m mcp.server
|
|
394
|
+
```
|
|
395
|
+
</details>
|
|
396
|
+
|
|
397
|
+
<details>
|
|
398
|
+
<summary><b>Antigravity IDE</b></summary>
|
|
399
|
+
|
|
400
|
+
Add to `~/.gemini/config/mcp_config.json`:
|
|
401
|
+
|
|
402
|
+
```json
|
|
403
|
+
{
|
|
404
|
+
"mcpServers": {
|
|
405
|
+
"friday": {
|
|
406
|
+
"command": "python",
|
|
407
|
+
"args": ["-m", "mcp.server"],
|
|
408
|
+
"cwd": "/path/to/friday",
|
|
409
|
+
"env": {
|
|
410
|
+
"FRIDAY_URL": "http://localhost:8000",
|
|
411
|
+
"BRAIN_API_KEY": "your_secret_key"
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
</details>
|
|
418
|
+
|
|
419
|
+
<details>
|
|
420
|
+
<summary><b>VS Code (Cline / Roo Code)</b></summary>
|
|
421
|
+
|
|
422
|
+
Add to your VS Code MCP configuration:
|
|
423
|
+
|
|
424
|
+
```json
|
|
425
|
+
{
|
|
426
|
+
"cline.mcpServers": {
|
|
427
|
+
"friday": {
|
|
428
|
+
"command": "python",
|
|
429
|
+
"args": ["-m", "mcp.server"],
|
|
430
|
+
"cwd": "/path/to/friday",
|
|
431
|
+
"env": {
|
|
432
|
+
"FRIDAY_URL": "http://localhost:8000",
|
|
433
|
+
"BRAIN_API_KEY": "your_secret_key"
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
</details>
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Tool Reference
|
|
444
|
+
|
|
445
|
+
Connected agents automatically access four core MCP primitives:
|
|
446
|
+
|
|
447
|
+
| Primitive | Purpose | Trigger Phase |
|
|
448
|
+
| :--- | :--- | :--- |
|
|
449
|
+
| `get_context` | Ingests active verified facts and recent context. | Session initialization. |
|
|
450
|
+
| `memory_search` | Queries vector and graph indices for architectural decisions. | Prior to answering technical questions. |
|
|
451
|
+
| `add_memory` | Records implementation details, rationale, and tradeoffs. | Post-implementation or bug resolution. |
|
|
452
|
+
| `add_fact` | Commits versioned, immutable ground truths (ports, stack, schemas). | Architectural declarations. |
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Dynamic Directives Export (`/export/persona`)
|
|
457
|
+
|
|
458
|
+
Friday can compile stored facts and architectural constraints into synchronized markdown directives on-demand, preventing rules drift across teams:
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
# Export canonical AGENTS.md
|
|
462
|
+
curl -s "http://localhost/export/persona?target=agents" \
|
|
463
|
+
-H "X-Brain-Key: your_key" > AGENTS.md
|
|
464
|
+
|
|
465
|
+
# Export Cursor .cursorrules
|
|
466
|
+
curl -s "http://localhost/export/persona?target=cursor" \
|
|
467
|
+
-H "X-Brain-Key: your_key" > .cursorrules
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Features
|
|
473
|
+
|
|
474
|
+
### 1. Automated Knowledge Graph Extraction
|
|
475
|
+
Every memory written via `add_memory` is analyzed asynchronously. Entities and typed relations are automatically wired into Neo4j without manual schema definitions:
|
|
476
|
+
|
|
477
|
+
```
|
|
478
|
+
Input:
|
|
479
|
+
"Billing engine connects to Stripe API for recurring charges. Webhook dispatched to /api/webhooks/stripe."
|
|
480
|
+
|
|
481
|
+
Extracted Graph Nodes & Edges:
|
|
482
|
+
(:Service {name: "BillingEngine"}) -[:CONNECTS_TO]-> (:API {name: "Stripe"})
|
|
483
|
+
(:API {name: "Stripe"}) -[:DISPATCHES_TO]-> (:Endpoint {path: "/api/webhooks/stripe"})
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### 2. Neural Studio (3D Topology Visualizer)
|
|
487
|
+
A browser-based WebGL graph explorer (Three.js) for auditing agent memory:
|
|
488
|
+
- **Cluster Topologies**: Visualizes architectural components as a 3D force-directed graph.
|
|
489
|
+
- **Entity Inspector**: Inspect node connections, versioned facts, and raw vector chunks.
|
|
490
|
+
- **Live CRUD**: Create, rename, or link entities directly within the visual interface.
|
|
491
|
+
- **High-Resolution Export**: Export topology diagrams for technical documentation.
|
|
492
|
+
|
|
493
|
+
### 3. Versioned Facts Ledger
|
|
494
|
+
Deterministic project constants are recorded with immutable version history. Outdated statements are superseded rather than overwritten, preserving an audit trail:
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
# Add initial constraint
|
|
498
|
+
POST /facts -> {"content": "PostgreSQL 16 running on port 5432"}
|
|
499
|
+
# Recorded: id="c41b8a9", superseded=false
|
|
500
|
+
|
|
501
|
+
# Update constraint
|
|
502
|
+
POST /facts -> {"content": "Migrated database to Aurora PostgreSQL on port 5432"}
|
|
503
|
+
# Prior fact marked superseded=true; active fact updated.
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## Repository Structure
|
|
509
|
+
|
|
510
|
+
```
|
|
511
|
+
friday/
|
|
512
|
+
├── gateway/ # FastAPI REST application & routing
|
|
513
|
+
├── layers/ # Pluggable storage adapters (SQLite, ChromaDB, Neo4j)
|
|
514
|
+
├── pipelines/ # Background entity extraction & fact pipelines
|
|
515
|
+
├── orchestrator/ # Multi-layer retrieval router
|
|
516
|
+
├── mcp/ # Model Context Protocol stdio server
|
|
517
|
+
├── studio/ # Three.js Neural Studio visualizer
|
|
518
|
+
├── benchmarks/ # DeepEval evaluation suite
|
|
519
|
+
├── tests/ # Pytest test suite
|
|
520
|
+
├── docker-compose.yml # Production container definition
|
|
521
|
+
├── Makefile # Developer task automation
|
|
522
|
+
└── pyproject.toml # Tooling & packaging configuration
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
---
|
|
526
|
+
|
|
527
|
+
## API Reference
|
|
528
|
+
|
|
529
|
+
All authenticated endpoints require the `X-Brain-Key` request header.
|
|
530
|
+
|
|
531
|
+
| Method | Path | Auth | Description |
|
|
532
|
+
| :---: | :--- | :---: | :--- |
|
|
533
|
+
| `GET` | `/` | No | Serves Neural Studio visualizer. |
|
|
534
|
+
| `GET` | `/health` | No | Layered health status check. |
|
|
535
|
+
| `POST` | `/add` | Yes | Ingest memory and trigger background graph extraction. |
|
|
536
|
+
| `POST` | `/facts` | Yes | Record or update a versioned fact. |
|
|
537
|
+
| `GET` | `/facts` | No | List active ground-truth facts. |
|
|
538
|
+
| `POST` | `/search` | Yes | Semantic search across vector stores. |
|
|
539
|
+
| `POST` | `/ingest` | Yes | Batch ingest architectural specifications. |
|
|
540
|
+
| `GET` | `/export/persona` | Yes | Export synchronized IDE rules (`agents` or `cursor`). |
|
|
541
|
+
| `GET` | `/api/graph-data` | No | Fetch nodes and edges for 3D visualizer. |
|
|
542
|
+
| `POST` | `/api/node/create` | Yes | Create a graph entity node. |
|
|
543
|
+
| `DELETE` | `/api/node/{id}` | Yes | Delete an entity and cascading relationships. |
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Development
|
|
548
|
+
|
|
549
|
+
```bash
|
|
550
|
+
# Install dependencies
|
|
551
|
+
make install
|
|
552
|
+
|
|
553
|
+
# Run test suite
|
|
554
|
+
make test
|
|
555
|
+
|
|
556
|
+
# Code formatting & linting
|
|
557
|
+
make lint
|
|
558
|
+
make format
|
|
559
|
+
|
|
560
|
+
# Start local dev server
|
|
561
|
+
make dev
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
566
|
+
## Contributing
|
|
567
|
+
|
|
568
|
+
Review [CONTRIBUTING.md](CONTRIBUTING.md) for pull request guidelines, commit conventions, and architectural standards.
|
|
569
|
+
|
|
570
|
+
---
|
|
571
|
+
|
|
572
|
+
## License
|
|
573
|
+
|
|
574
|
+
Friday is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
friday/__init__.py,sha256=RxeiEKB0c7iFo3_kreu2vcAuGda11KGloZd4q8064pU,718
|
|
2
|
+
friday/client.py,sha256=30uI5r2hTgSGGAT6-rCberKgNX90aC6p7j7iwQdp4g0,11893
|
|
3
|
+
friday/exceptions.py,sha256=U2jTIZk-71i2IK4555CGSw7BuI6ZXsu0eYP5wCpto80,903
|
|
4
|
+
friday/types.py,sha256=DqRzODKbsvntfWmvTxFGqe3gAUrnIq0Ky02g_5Ec8Hc,802
|
|
5
|
+
friday/integrations/__init__.py,sha256=e7f70LYhSEhrUkdDstgUluF8Wz2EXIbY85ukBx7Rx5w,52
|
|
6
|
+
friday/integrations/langchain.py,sha256=T40JaQKxXkcAqO_IwOEVqQeMx23fbi1H4bb3IV1b8bw,2828
|
|
7
|
+
friday_memory-1.2.0.dist-info/METADATA,sha256=N2cYnddeJL2q_fLEfayNQrGPqGuODwGJyDwrnm_Q-PM,24283
|
|
8
|
+
friday_memory-1.2.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
9
|
+
friday_memory-1.2.0.dist-info/licenses/LICENSE,sha256=rxQEFijI8CsGZ6vgRmUDavcaHp6y5yeii-6RXrYTkfc,1076
|
|
10
|
+
friday_memory-1.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Friday Contributors
|
|
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.
|