hmg-sdk 0.9.2__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.
hmg/__init__.py ADDED
@@ -0,0 +1,387 @@
1
+ """HMG Agent Memory SDK — Community Edition.
2
+
3
+ Wire-safe client types for the HMG agent memory protocol.
4
+ This module contains only the public DTO types needed to interact
5
+ with HMG Community Edition via HTTP or MCP.
6
+
7
+ For the full SDK including observation, vault, and advanced features,
8
+ see the Developer/Enterprise Edition.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field, asdict
14
+ from typing import Any, Literal, TypeAlias
15
+ from urllib import request
16
+ import json
17
+
18
+ ActorKind = Literal["User", "Agent", "Service"]
19
+ AccessLevel = Literal["Public", "Internal", "Restricted"]
20
+ CorrectAction = Literal["negate", "confirm_actual", "confirm_necessary", "demote", "replace"]
21
+ GovernanceAction = Literal["quarantine", "seal", "tombstone", "derive_lesson"]
22
+ RecallViewMode = Literal["normal", "governance", "audit"]
23
+ Modality = Literal["text", "code", "dialogue", "observation"]
24
+ OutputFormat = Literal["compact_yaml", "json", "markdown", "summary"]
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Context types
29
+ # ---------------------------------------------------------------------------
30
+
31
+ @dataclass
32
+ class ScopeSegment:
33
+ """One segment in a hierarchical scope path."""
34
+ kind: str
35
+ id: str
36
+
37
+
38
+ @dataclass
39
+ class ScopeRef:
40
+ """A hierarchical scope reference."""
41
+ tenant_id: str
42
+ path: list[ScopeSegment] = field(default_factory=list)
43
+
44
+ @staticmethod
45
+ def coding_agent(tenant_id: str, workspace: str, repository: str, branch: str) -> "ScopeRef":
46
+ """Create a software-engineering style scope."""
47
+ return ScopeRef(
48
+ tenant_id=tenant_id,
49
+ path=[
50
+ ScopeSegment(kind="workspace", id=workspace),
51
+ ScopeSegment(kind="repository", id=repository),
52
+ ScopeSegment(kind="branch", id=branch),
53
+ ],
54
+ )
55
+
56
+
57
+ @dataclass
58
+ class ActorPrincipal:
59
+ kind: ActorKind
60
+ id: str
61
+
62
+
63
+ @dataclass
64
+ class AuditContext:
65
+ actor: ActorPrincipal
66
+ operation: str
67
+ trace_id: str | None = None
68
+ reason: str | None = None
69
+
70
+
71
+ @dataclass
72
+ class EffectiveTimeWindow:
73
+ starts_at: str | None = None
74
+ ends_at: str | None = None
75
+
76
+
77
+ @dataclass
78
+ class ObjectRef:
79
+ object_type: str
80
+ object_id: str
81
+
82
+
83
+ @dataclass
84
+ class MemoryReferences:
85
+ subjects: list[ObjectRef] = field(default_factory=list)
86
+ artifacts: list[ObjectRef] = field(default_factory=list)
87
+ work_items: list[ObjectRef] = field(default_factory=list)
88
+ decisions: list[ObjectRef] = field(default_factory=list)
89
+ commitments: list[ObjectRef] = field(default_factory=list)
90
+ evidence: list[ObjectRef] = field(default_factory=list)
91
+
92
+
93
+ @dataclass
94
+ class RetentionPolicy:
95
+ kind: Literal["keep_indefinitely", "retain_until"] = "keep_indefinitely"
96
+ until: str | None = None
97
+
98
+
99
+ @dataclass
100
+ class MemoryGovernance:
101
+ sensitivity: list[str] = field(default_factory=list)
102
+ legal_basis: str | None = None
103
+ retention_policy: RetentionPolicy | None = None
104
+ break_glass_role: str | None = None
105
+
106
+
107
+ @dataclass
108
+ class MemoryContext:
109
+ """Unified context attached to memory operations."""
110
+ scope: ScopeRef | None = None
111
+ access_level: AccessLevel | None = None
112
+ policy_tags: list[str] = field(default_factory=list)
113
+ effective_time: EffectiveTimeWindow | None = None
114
+ audit: AuditContext | None = None
115
+ references: MemoryReferences | None = None
116
+ governance: MemoryGovernance | None = None
117
+
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # Request types
121
+ # ---------------------------------------------------------------------------
122
+
123
+ @dataclass
124
+ class MemorizeRequest:
125
+ content: str
126
+ source: str | None = None
127
+ modality: Modality | None = None
128
+ domain_pack_id: str | None = None
129
+ context: MemoryContext | None = None
130
+
131
+
132
+ @dataclass
133
+ class RecallRequest:
134
+ query: str
135
+ max_results: int | None = None
136
+ include_negated: bool | None = None
137
+ domain_pack_id: str | None = None
138
+ context: MemoryContext | None = None
139
+
140
+
141
+ @dataclass
142
+ class RecallViewRequest(RecallRequest):
143
+ mode: RecallViewMode | None = None
144
+
145
+
146
+ @dataclass
147
+ class CorrectRequest:
148
+ target_atom: str
149
+ action: CorrectAction
150
+ reason: str
151
+ new_content: str | None = None
152
+ domain_pack_id: str | None = None
153
+ context: MemoryContext | None = None
154
+
155
+
156
+ @dataclass
157
+ class GovernanceRequest:
158
+ target_atom: str
159
+ action: GovernanceAction
160
+ reason: str
161
+ actor: str | None = None
162
+ lesson_content: str | None = None
163
+ destroy_payload: bool | None = None
164
+
165
+
166
+ @dataclass
167
+ class HandoffRequest:
168
+ summary: str
169
+ source: str | None = None
170
+ context: MemoryContext | None = None
171
+
172
+
173
+ @dataclass
174
+ class AgentBriefRequest:
175
+ query: str | None = None
176
+ max_results: int | None = None
177
+ output_format: OutputFormat | None = None
178
+ context: MemoryContext | None = None
179
+
180
+
181
+ # ---------------------------------------------------------------------------
182
+ # Response types
183
+ # ---------------------------------------------------------------------------
184
+
185
+ @dataclass
186
+ class ApiError:
187
+ code: str
188
+ message: str
189
+ details: dict | None = None
190
+
191
+
192
+ @dataclass
193
+ class MemorizeResponse:
194
+ added_atoms: list[str] = field(default_factory=list)
195
+ snapshot_version: int | None = None
196
+ error: str | None = None
197
+
198
+
199
+ @dataclass
200
+ class RecalledAtom:
201
+ id: str
202
+ text: str | None = None
203
+ score: float | None = None
204
+ epistemic_rank: int | None = None
205
+ is_conditional: bool | None = None
206
+ exposure_state: str | None = None
207
+
208
+
209
+ @dataclass
210
+ class RecallResponse:
211
+ narrative: str | None = None
212
+ atoms: list[RecalledAtom] = field(default_factory=list)
213
+ knowledge_gaps: list[str] = field(default_factory=list)
214
+ candidates_considered: int | None = None
215
+ error: str | None = None
216
+
217
+
218
+ @dataclass
219
+ class CorrectResponse:
220
+ success: bool
221
+ message: str
222
+ replacement_atom: str | None = None
223
+
224
+
225
+ @dataclass
226
+ class GovernanceResponse:
227
+ success: bool
228
+ message: str
229
+ target_atom: str
230
+ lesson_atom: str | None = None
231
+ exposure: str | None = None
232
+
233
+
234
+ @dataclass
235
+ class AtomHistory:
236
+ atom: dict
237
+ current: dict
238
+ polarity_history: list = field(default_factory=list)
239
+ epistemic_history: list = field(default_factory=list)
240
+ exposure_history: list = field(default_factory=list)
241
+
242
+
243
+ @dataclass
244
+ class StatsResponse:
245
+ atom_count: int
246
+ edge_count: int
247
+ snapshot_version: int
248
+
249
+
250
+ @dataclass
251
+ class HandoffResponse:
252
+ summary: str
253
+ atoms_stored: int
254
+ scope: str | None = None
255
+
256
+
257
+ @dataclass
258
+ class AgentBriefResponse:
259
+ content: str
260
+ memory_count: int
261
+ decisions: list[str] = field(default_factory=list)
262
+ risks: list[str] = field(default_factory=list)
263
+ next_steps: list[str] = field(default_factory=list)
264
+
265
+
266
+ # ---------------------------------------------------------------------------
267
+ # Client
268
+ # ---------------------------------------------------------------------------
269
+
270
+ @dataclass
271
+ class ApiEnvelope:
272
+ ok: bool
273
+ data: dict | None = None
274
+ error: ApiError | None = None
275
+
276
+
277
+ ApiEnvelopeDict: TypeAlias = dict[str, Any]
278
+
279
+
280
+ def api_envelope_data(envelope: ApiEnvelopeDict) -> Any | None:
281
+ return envelope.get("data")
282
+
283
+
284
+ def api_envelope_error(envelope: ApiEnvelopeDict) -> dict[str, Any] | None:
285
+ error = envelope.get("error")
286
+ return error if isinstance(error, dict) else None
287
+
288
+
289
+ class HMGClient:
290
+ """HTTP client for the HMG memory service.
291
+
292
+ Usage:
293
+ client = HMGClient(base_url="http://localhost:8080")
294
+ client.memorize(content="We chose PostgreSQL for the main database")
295
+ result = client.recall(query="database choice")
296
+ for atom in result.atoms:
297
+ print(atom.content)
298
+ """
299
+
300
+ def __init__(self, base_url: str = "http://localhost:8080", api_key: str | None = None):
301
+ self.base_url = base_url.rstrip("/")
302
+ self.api_key = api_key
303
+
304
+ def _request(self, method: str, path: str, body: dict | None = None) -> dict:
305
+ url = f"{self.base_url}{path}"
306
+ headers = {"Content-Type": "application/json"}
307
+ if self.api_key:
308
+ headers["x-api-key"] = self.api_key
309
+
310
+ data = json.dumps(body).encode() if body else None
311
+ req = request.Request(url, data=data, headers=headers, method=method)
312
+ with request.urlopen(req) as resp:
313
+ return json.loads(resp.read())
314
+
315
+ def memorize(self, content: str, **kwargs) -> MemorizeResponse:
316
+ """Store a new memory atom."""
317
+ req = MemorizeRequest(content=content, **kwargs)
318
+ envelope = self._request("POST", "/api/memorize", _to_dict(req))
319
+ if envelope.get("data"):
320
+ return MemorizeResponse(**envelope["data"])
321
+ raise _api_error(envelope)
322
+
323
+ def recall(self, query: str, **kwargs) -> RecallResponse:
324
+ """Recall relevant memories."""
325
+ req = RecallRequest(query=query, **kwargs)
326
+ envelope = self._request("POST", "/api/recall", _to_dict(req))
327
+ if envelope.get("data"):
328
+ data = envelope["data"]
329
+ data["atoms"] = [RecalledAtom(**a) for a in data.get("atoms", [])]
330
+ return RecallResponse(**data)
331
+ raise _api_error(envelope)
332
+
333
+ def recall_view(self, query: str, mode: RecallViewMode = "normal", **kwargs) -> RecallResponse:
334
+ """Recall memories through a governance-aware view."""
335
+ req = RecallViewRequest(query=query, mode=mode, **kwargs)
336
+ envelope = self._request("POST", "/api/recall_view", _to_dict(req))
337
+ if envelope.get("data"):
338
+ data = envelope["data"]
339
+ data["atoms"] = [RecalledAtom(**a) for a in data.get("atoms", [])]
340
+ return RecallResponse(**data)
341
+ raise _api_error(envelope)
342
+
343
+ def correct(self, target_atom: str, action: CorrectAction, reason: str, **kwargs) -> CorrectResponse:
344
+ """Correct an existing memory atom."""
345
+ req = CorrectRequest(target_atom=target_atom, action=action, reason=reason, **kwargs)
346
+ envelope = self._request("POST", "/api/correct", _to_dict(req))
347
+ if envelope.get("data"):
348
+ return CorrectResponse(**envelope["data"])
349
+ raise _api_error(envelope)
350
+
351
+ def govern(self, target_atom: str, action: GovernanceAction, reason: str, **kwargs) -> GovernanceResponse:
352
+ """Apply a governance action to a memory atom."""
353
+ req = GovernanceRequest(target_atom=target_atom, action=action, reason=reason, **kwargs)
354
+ envelope = self._request("POST", f"/api/governance/{action}", _to_dict(req))
355
+ if envelope.get("data"):
356
+ return GovernanceResponse(**envelope["data"])
357
+ raise _api_error(envelope)
358
+
359
+ def history(self, atom_id: str) -> AtomHistory:
360
+ """Inspect atom correction and governance history."""
361
+ envelope = self._request("GET", f"/api/atom/{atom_id}/history")
362
+ if envelope.get("data"):
363
+ return AtomHistory(**envelope["data"])
364
+ raise _api_error(envelope)
365
+
366
+ def stats(self) -> StatsResponse:
367
+ """Get memory graph statistics."""
368
+ envelope = self._request("GET", "/api/stats")
369
+ if envelope.get("data"):
370
+ return StatsResponse(**envelope["data"])
371
+ raise _api_error(envelope)
372
+
373
+ def graph_export(self) -> dict:
374
+ """Export the memory graph as visualization-friendly JSON."""
375
+ envelope = self._request("GET", "/api/graph/export")
376
+ return envelope.get("data", {})
377
+
378
+
379
+ def _to_dict(obj: Any) -> dict:
380
+ """Convert a dataclass to a dict, filtering None values."""
381
+ d = asdict(obj)
382
+ return {k: v for k, v in d.items() if v is not None}
383
+
384
+
385
+ def _api_error(envelope: dict) -> Exception:
386
+ error = envelope.get("error", {})
387
+ return Exception(f"HMG API error: {error.get('code', 'unknown')} — {error.get('message', 'no details')}")
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: hmg-sdk
3
+ Version: 0.9.2
4
+ Summary: HMG Agent Memory SDK — Community Edition
5
+ License-Expression: Apache-2.0
6
+ Project-URL: Repository, https://github.com/HMG-AI/HMG-public
7
+ Keywords: hmg,memory,agent,protocol,ai
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Topic :: Software Development :: Libraries
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+
15
+ # hmg
16
+
17
+ <p>
18
+ <img src="https://img.shields.io/badge/version-0.9.2-blue.svg" alt="Version">
19
+ <img src="https://img.shields.io/badge/license-Apache--2.0-green.svg" alt="License">
20
+ <img src="https://img.shields.io/badge/python-3.9%2B-blue.svg" alt="Python">
21
+ </p>
22
+
23
+ Python SDK for the HMG agent memory system.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install hmg
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```python
34
+ from hmg import HMGClient
35
+
36
+ client = HMGClient(base_url="http://localhost:8080")
37
+
38
+ # Store a decision
39
+ client.memorize(
40
+ content="We chose PostgreSQL for the main database",
41
+ source="architecture-review",
42
+ modality="text",
43
+ )
44
+
45
+ # Recall it later
46
+ result = client.recall(query="database choice")
47
+ for atom in result.atoms:
48
+ print(f"[{atom.score:.2f}] {atom.text}")
49
+
50
+ # Correct when it changes
51
+ client.correct(
52
+ atom_id=atom.id,
53
+ action="replace",
54
+ reason="Migrated to CockroachDB",
55
+ new_content="We migrated to CockroachDB for horizontal scale",
56
+ )
57
+ ```
58
+
59
+ ## API Surface
60
+
61
+ | Method | Description |
62
+ |---|---|
63
+ | `client.memorize(...)` | Store a memory atom |
64
+ | `client.recall(...)` | Recall memories by query |
65
+ | `client.correct(...)` | Correct a memory atom |
66
+ | `client.govern(...)` | Govern a memory atom's visibility |
67
+ | `client.handoff(...)` | Store a cross-session handoff |
68
+ | `client.agent_brief(...)` | Get session-start context |
69
+ | `client.history(...)` | Get correction/governance history |
70
+ | `client.stats()` | Get memory store statistics |
71
+
72
+ ## Requirements
73
+
74
+ - Python 3.9+
75
+ - HMG daemon running (`hmg daemon start`)
76
+
77
+ ## License
78
+
79
+ Apache-2.0
@@ -0,0 +1,5 @@
1
+ hmg/__init__.py,sha256=P8hRj_FNOlvspGDHr5m6Iihh6qntKZNO_z9tJ8yq5X0,11665
2
+ hmg_sdk-0.9.2.dist-info/METADATA,sha256=gZyuapOyfcrg--CYjYQH3a7g_RPKiYF_HKEkaHlXZsM,2022
3
+ hmg_sdk-0.9.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
4
+ hmg_sdk-0.9.2.dist-info/top_level.txt,sha256=C51-jV1UvbUb72_uJLKqO0C9chsP0iQnP1hnopSeONA,4
5
+ hmg_sdk-0.9.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ hmg