adaptive-memory-engine 0.1.6__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.
Files changed (72) hide show
  1. adaptive_memory_engine-0.1.6.dist-info/METADATA +228 -0
  2. adaptive_memory_engine-0.1.6.dist-info/RECORD +72 -0
  3. adaptive_memory_engine-0.1.6.dist-info/WHEEL +4 -0
  4. adaptive_memory_engine-0.1.6.dist-info/entry_points.txt +3 -0
  5. adaptive_memory_engine-0.1.6.dist-info/licenses/LICENSE +21 -0
  6. ame/__init__.py +1 -0
  7. ame/agent/__init__.py +1 -0
  8. ame/agent/mcp.py +474 -0
  9. ame/agent/memory_api.py +141 -0
  10. ame/agent/results.py +30 -0
  11. ame/bronze/schema.py +17 -0
  12. ame/bronze/store.py +38 -0
  13. ame/cli/__init__.py +1 -0
  14. ame/cli/main.py +903 -0
  15. ame/connectors/base.py +30 -0
  16. ame/connectors/contract.py +199 -0
  17. ame/connectors/github.py +66 -0
  18. ame/connectors/google.py +464 -0
  19. ame/connectors/google_oauth.py +156 -0
  20. ame/connectors/jira.py +66 -0
  21. ame/connectors/json_helpers.py +43 -0
  22. ame/connectors/markdown.py +116 -0
  23. ame/connectors/notion.py +59 -0
  24. ame/connectors/oauth_callback.py +102 -0
  25. ame/connectors/oauth_provider.py +250 -0
  26. ame/connectors/obsidian.py +19 -0
  27. ame/connectors/router.py +155 -0
  28. ame/connectors/slack.py +66 -0
  29. ame/connectors/slack_oauth.py +417 -0
  30. ame/connectors/sync_history.py +73 -0
  31. ame/context_budget.py +106 -0
  32. ame/core/config.py +77 -0
  33. ame/core/corpus.py +17 -0
  34. ame/core/errors.py +18 -0
  35. ame/core/paths.py +111 -0
  36. ame/core/state.py +57 -0
  37. ame/export/obsidian.py +123 -0
  38. ame/gold/builder.py +300 -0
  39. ame/gold/ontology.py +80 -0
  40. ame/gold/resolver.py +91 -0
  41. ame/gold/schema.py +40 -0
  42. ame/gold/store.py +45 -0
  43. ame/hardware/profiler.py +85 -0
  44. ame/hardware/tier.py +27 -0
  45. ame/hermes/__init__.py +3 -0
  46. ame/hermes/memory.py +209 -0
  47. ame/models/download.py +243 -0
  48. ame/models/ollama.py +60 -0
  49. ame/models/registry.py +101 -0
  50. ame/models/router.py +22 -0
  51. ame/pipeline.py +155 -0
  52. ame/query/diff.py +40 -0
  53. ame/query/engine.py +919 -0
  54. ame/query/memory_os.py +313 -0
  55. ame/query/mql.py +84 -0
  56. ame/query/multihop.py +264 -0
  57. ame/query/result.py +20 -0
  58. ame/sdk.py +52 -0
  59. ame/security.py +145 -0
  60. ame/silver/extractor.py +414 -0
  61. ame/silver/llm_extractor.py +181 -0
  62. ame/silver/prompts.py +56 -0
  63. ame/silver/rationale.py +140 -0
  64. ame/silver/schema.py +51 -0
  65. ame/silver/store.py +59 -0
  66. ame/storage/custom_kg.py +33 -0
  67. ame/storage/lightrag_adapter.py +362 -0
  68. ame/validation/confidence.py +5 -0
  69. ame/validation/grounding.py +10 -0
  70. ame/validation/type_gate.py +22 -0
  71. ame/writeback.py +173 -0
  72. memory/__init__.py +3 -0
ame/connectors/base.py ADDED
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from pathlib import Path
5
+ from typing import Any, Protocol
6
+
7
+ from ame.bronze.schema import BronzeDocument
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class SourceRef:
12
+ path: Path
13
+ source_id: str
14
+ content: str | None = None
15
+ section_title: str | None = None
16
+ section_path: tuple[str, ...] = ()
17
+ section_level: int | None = None
18
+ section_index: int | None = None
19
+ root_source_id: str | None = None
20
+ metadata: dict[str, Any] = field(default_factory=dict)
21
+
22
+
23
+ class Connector(Protocol):
24
+ source_type: str
25
+
26
+ def scan(self, path: Path) -> list[SourceRef]:
27
+ ...
28
+
29
+ def load(self, corpus_id: str, ref: SourceRef) -> BronzeDocument:
30
+ ...
@@ -0,0 +1,199 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from pathlib import Path
5
+ from typing import Literal, Protocol
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from ame.bronze.schema import BronzeDocument
10
+ from ame.bronze.store import BronzeStore
11
+ from ame.connectors.base import Connector, SourceRef
12
+ from ame.connectors.sync_history import ConnectorSyncStore
13
+ from ame.core.corpus import require_corpus
14
+
15
+
16
+ class ConnectorProfile(BaseModel):
17
+ name: str
18
+ source_type: str
19
+ mode: Literal["export", "live"] = "export"
20
+ version: str = "1"
21
+ features: list[str] = Field(default_factory=list)
22
+ contract: list[str] = Field(
23
+ default_factory=lambda: ["connect", "fetch", "normalize_to_bronze", "diff", "sync"]
24
+ )
25
+
26
+
27
+ class ConnectorConnection(BaseModel):
28
+ profile: ConnectorProfile
29
+ path: Path
30
+ connected_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
31
+
32
+
33
+ class ConnectorDiff(BaseModel):
34
+ connector: str
35
+ source_type: str
36
+ source_path: Path
37
+ fetched: int
38
+ new: list[str] = Field(default_factory=list)
39
+ changed: list[str] = Field(default_factory=list)
40
+ unchanged: list[str] = Field(default_factory=list)
41
+ removed: list[str] = Field(default_factory=list)
42
+
43
+ @property
44
+ def counts(self) -> dict[str, int]:
45
+ return {
46
+ "fetched": self.fetched,
47
+ "new": len(self.new),
48
+ "changed": len(self.changed),
49
+ "unchanged": len(self.unchanged),
50
+ "removed": len(self.removed),
51
+ }
52
+
53
+
54
+ class ConnectorContractSyncReport(BaseModel):
55
+ corpus_id: str
56
+ connector: str
57
+ source_type: str
58
+ source_path: Path
59
+ fetched: int
60
+ new: int
61
+ changed: int
62
+ unchanged: int
63
+ removed: int
64
+ ingested_documents: int
65
+ sync_run_id: str | None = None
66
+
67
+
68
+ class ContractConnector(Protocol):
69
+ profile: ConnectorProfile
70
+
71
+ def connect(self, path: Path) -> ConnectorConnection:
72
+ ...
73
+
74
+ def fetch(self, path: Path) -> list[SourceRef]:
75
+ ...
76
+
77
+ def normalize_to_bronze(self, corpus_id: str, ref: SourceRef) -> BronzeDocument:
78
+ ...
79
+
80
+ def diff(self, corpus_id: str, path: Path) -> ConnectorDiff:
81
+ ...
82
+
83
+ def sync(self, corpus_id: str, path: Path) -> ConnectorContractSyncReport:
84
+ ...
85
+
86
+
87
+ class ExportConnectorRuntime:
88
+ def __init__(self, connector: Connector, profile: ConnectorProfile):
89
+ self.connector = connector
90
+ self.profile = profile
91
+
92
+ def connect(self, path: Path) -> ConnectorConnection:
93
+ return ConnectorConnection(profile=self.profile, path=path.expanduser().resolve())
94
+
95
+ def fetch(self, path: Path) -> list[SourceRef]:
96
+ return self.connector.scan(path)
97
+
98
+ def normalize_to_bronze(self, corpus_id: str, ref: SourceRef) -> BronzeDocument:
99
+ return self.connector.load(corpus_id, ref)
100
+
101
+ def diff(self, corpus_id: str, path: Path) -> ConnectorDiff:
102
+ corpus_root = require_corpus(corpus_id)
103
+ refs = self.fetch(path)
104
+ fetched_docs = [self.normalize_to_bronze(corpus_id, ref) for ref in refs]
105
+ existing_docs = [doc for doc in BronzeStore(corpus_root).list() if doc.source_type == self.connector.source_type]
106
+ existing_by_source = {doc.source_id: doc for doc in existing_docs}
107
+ fetched_source_ids = {doc.source_id for doc in fetched_docs}
108
+
109
+ new: list[str] = []
110
+ changed: list[str] = []
111
+ unchanged: list[str] = []
112
+ for doc in fetched_docs:
113
+ existing = existing_by_source.get(doc.source_id)
114
+ if existing is None:
115
+ new.append(doc.source_id)
116
+ elif existing.content_hash == doc.content_hash:
117
+ unchanged.append(doc.source_id)
118
+ else:
119
+ changed.append(doc.source_id)
120
+
121
+ removed = sorted(doc.source_id for doc in existing_docs if doc.source_id not in fetched_source_ids)
122
+ return ConnectorDiff(
123
+ connector=self.profile.name,
124
+ source_type=self.connector.source_type,
125
+ source_path=path,
126
+ fetched=len(refs),
127
+ new=sorted(new),
128
+ changed=sorted(changed),
129
+ unchanged=sorted(unchanged),
130
+ removed=removed,
131
+ )
132
+
133
+ def sync(self, corpus_id: str, path: Path) -> ConnectorContractSyncReport:
134
+ corpus_root = require_corpus(corpus_id)
135
+ started_at = datetime.now(timezone.utc)
136
+ sync_store = ConnectorSyncStore(corpus_root)
137
+ diff = self.diff(corpus_id, path)
138
+ ingested = 0
139
+ try:
140
+ from ame.pipeline import MemoryPipeline
141
+
142
+ ingest_report = MemoryPipeline().ingest(corpus_id, path, profile=self.profile.name)
143
+ ingested = ingest_report.documents
144
+ run = sync_store.record(
145
+ connector=self.profile.name,
146
+ status="success",
147
+ started_at=started_at,
148
+ source=str(path),
149
+ counts={
150
+ "fetched": diff.fetched,
151
+ "new": len(diff.new),
152
+ "changed": len(diff.changed),
153
+ "unchanged": len(diff.unchanged),
154
+ "removed": len(diff.removed),
155
+ "ingested_documents": ingested,
156
+ },
157
+ metadata={"source_type": self.connector.source_type, "profile": self.profile.name},
158
+ )
159
+ return self._report(corpus_id, path, diff, ingested, run.id)
160
+ except Exception as exc:
161
+ sync_store.record(
162
+ connector=self.profile.name,
163
+ status="failed",
164
+ started_at=started_at,
165
+ source=str(path),
166
+ counts={
167
+ "fetched": diff.fetched,
168
+ "new": len(diff.new),
169
+ "changed": len(diff.changed),
170
+ "unchanged": len(diff.unchanged),
171
+ "removed": len(diff.removed),
172
+ "ingested_documents": ingested,
173
+ },
174
+ metadata={"source_type": self.connector.source_type, "profile": self.profile.name},
175
+ error=str(exc),
176
+ )
177
+ raise
178
+
179
+ def _report(
180
+ self,
181
+ corpus_id: str,
182
+ path: Path,
183
+ diff: ConnectorDiff,
184
+ ingested_documents: int,
185
+ sync_run_id: str,
186
+ ) -> ConnectorContractSyncReport:
187
+ return ConnectorContractSyncReport(
188
+ corpus_id=corpus_id,
189
+ connector=self.profile.name,
190
+ source_type=self.connector.source_type,
191
+ source_path=path,
192
+ fetched=diff.fetched,
193
+ new=len(diff.new),
194
+ changed=len(diff.changed),
195
+ unchanged=len(diff.unchanged),
196
+ removed=len(diff.removed),
197
+ ingested_documents=ingested_documents,
198
+ sync_run_id=sync_run_id,
199
+ )
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from ame.bronze.schema import BronzeDocument
8
+ from ame.connectors.base import SourceRef
9
+ from ame.connectors.json_helpers import as_list, first_present, read_json
10
+
11
+
12
+ class GitHubConnector:
13
+ source_type = "github"
14
+
15
+ def scan(self, path: Path) -> list[SourceRef]:
16
+ root = path.expanduser().resolve()
17
+ files = [root] if root.is_file() else sorted(root.rglob("*.json"))
18
+ refs: list[SourceRef] = []
19
+ for file in files:
20
+ for row in as_list(read_json(file)):
21
+ if not isinstance(row, dict):
22
+ continue
23
+ number = str(first_present(row, "number", "id") or file.stem)
24
+ kind = str(first_present(row, "kind", "type") or ("pull_request" if row.get("pull_request") else "issue"))
25
+ refs.append(SourceRef(path=file, source_id=f"github:{kind}:{number}", content=self._item_content(row, kind, number)))
26
+ return refs
27
+
28
+ def load(self, corpus_id: str, ref: SourceRef) -> BronzeDocument:
29
+ content = ref.content or ref.path.read_text(encoding="utf-8")
30
+ digest = hashlib.sha256(content.encode("utf-8")).hexdigest()
31
+ return BronzeDocument(
32
+ id=f"bronze_{digest[:16]}",
33
+ corpus_id=corpus_id,
34
+ source_type=self.source_type,
35
+ source_id=ref.source_id,
36
+ content=content,
37
+ metadata={"path": str(ref.path), "title": ref.source_id, "connector": "github"},
38
+ content_hash=f"sha256:{digest}",
39
+ )
40
+
41
+ def _item_content(self, row: dict[str, Any], kind: str, number: str) -> str:
42
+ title = str(first_present(row, "title", "summary", "name") or f"{kind} {number}")
43
+ body = str(first_present(row, "body", "description", "content") or "")
44
+ state = first_present(row, "state", "status")
45
+ comments = first_present(row, "comments", "discussion") or []
46
+ if isinstance(comments, dict):
47
+ comments = as_list(comments)
48
+ comment_text = "\n".join(f"- {first_present(comment, 'body', 'text', 'content')}" for comment in comments if isinstance(comment, dict))
49
+ return "\n".join(
50
+ [
51
+ "---",
52
+ f"title: {title}",
53
+ f"github_kind: {kind}",
54
+ f"github_number: {number}",
55
+ f"state: {state or ''}",
56
+ "---",
57
+ "",
58
+ f"# {title}",
59
+ "",
60
+ body,
61
+ "",
62
+ "## Comments",
63
+ comment_text,
64
+ "",
65
+ ]
66
+ )