aegisdb 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aegisdb-0.8.0/PKG-INFO ADDED
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: aegisdb
3
+ Version: 0.8.0
4
+ Summary: Python client for AegisDB — persistent memory for AI agents
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/d4n-larsson/aegisdb
7
+ Project-URL: Repository, https://github.com/d4n-larsson/aegisdb
8
+ Project-URL: Issues, https://github.com/d4n-larsson/aegisdb/issues
9
+ Project-URL: Documentation, https://github.com/d4n-larsson/aegisdb/blob/main/docs/wire-protocol.md
10
+ Keywords: aegisdb,agent-memory,vector-search,llm,memory
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7; extra == "dev"
15
+
16
+ # aegisdb — Python client
17
+
18
+ The client for [AegisDB](https://github.com/d4n-larsson/aegisdb)'s
19
+ newline-delimited JSON protocol. **No dependencies** — AegisDB is a single
20
+ dependency-free binary and its wire protocol is one JSON object per line over
21
+ TCP, so the client that talks to it has no business dragging in a tree.
22
+
23
+ ```bash
24
+ pip install aegisdb
25
+ ```
26
+
27
+ ```python
28
+ from aegisdb import AegisClient, NotFound
29
+
30
+ with AegisClient(host="127.0.0.1", port=9470, token="…") as db:
31
+ rec = db.insert("prefers dark mode", type="semantic", tags=["user"])
32
+ print(db.search(query="dark mode", top_k=5)["records"])
33
+ try:
34
+ db.get(999)
35
+ except NotFound as exc:
36
+ print(exc.code, exc.message)
37
+ ```
38
+
39
+ Every wire operation has a method — `insert` / `insert_many`, `get`, `history`,
40
+ `update`, `delete`, `search`, `count`, `consolidate`, `forget`, `export`,
41
+ `purge`, `promote`, `relate`, `traverse`, `conflicts`, `ping`, `stats`,
42
+ `snapshot`, and the token admin trio. Each accepts only the fields the server
43
+ actually reads (the list was taken from the dispatcher, not from prose), plus
44
+ `**extra` as the escape hatch for a field a newer server understands.
45
+
46
+ ## What it does that a bare socket doesn't
47
+
48
+ **Errors are exceptions, one class per wire code.** `NotFound`, `Forbidden`,
49
+ `NotReady`, `RateLimited`, `MemoryLimit`, and the rest — all under
50
+ `AegisRequestError`, which carries `.code` and `.message` verbatim so a code
51
+ this client predates still arrives catchable rather than as a string you compare
52
+ by hand. `AegisUnavailable` is deliberately *not* one of them: a refusal means
53
+ the server did not act, while an unanswered request says nothing either way, and
54
+ that difference is what you reason about when deciding whether to retry.
55
+
56
+ **One connection, reused.** The server supports pipelining and this client
57
+ deliberately does not use it: one line out, one line back, so a response is
58
+ never mistaken for the tail of another. A reused connection that fails with no
59
+ response received is retried once on a fresh one, because the server reaps
60
+ connections idle past `--idle-timeout-sec` and that is exactly what a pause
61
+ between calls looks like.
62
+
63
+ That retry is safe for the case it exists for — a reaped connection never
64
+ delivered the request. It is not safe in general: if the server received the
65
+ request and the answer was lost, a retried `insert` writes a second record.
66
+ Pass `retry_stale=False` where that matters more than the convenience, or
67
+ `reuse=False` for a fresh connection per request.
68
+
69
+ **Not thread-safe.** A client owns one socket. Use one per thread, or
70
+ `reuse=False`.
71
+
72
+ **An unspecified argument is omitted, not defaulted.** `None` means "not
73
+ specified", so the *server's* default applies rather than a copy of it kept
74
+ here — two copies drift, and the client's would silently win. Falsy values are
75
+ not treated as absent: `limit=0` is the `conflicts` count-without-listing probe,
76
+ and `subsume=False` means something.
77
+
78
+ ## `agent_id` does not scope everything
79
+
80
+ `AegisClient(agent_id="…")` is applied to every request that does not name its
81
+ own, mirroring how the server scopes reads and writes. But `consolidate`,
82
+ `forget`, `update`, `delete`, `relate` and `promote` are scoped by the
83
+ **token's** namespace and ignore `agent_id` entirely — so with authentication
84
+ off they act across the whole server whatever you set it to. That asymmetry is
85
+ the server's, not this client's; the affected methods say so in their
86
+ docstrings.
87
+
88
+ ## Version
89
+
90
+ Published from the same `git tag` as the server and the Claude Code
91
+ integration, so `aegisdb`, `aegisdb-mcp` and the server binary all carry the
92
+ same version.
93
+
94
+ ## Tests
95
+
96
+ ```bash
97
+ python3 -m unittest discover -s tests # from clients/python/
98
+ make sdk-test # from the repo root
99
+ ```
100
+
101
+ `test_protocol.py` runs against a fake server and needs nothing. `test_live.py`
102
+ exercises **every** method against a real `build/aegisdb` and skips when it is
103
+ not built — which is the point: the server ignores request fields it does not
104
+ recognise, so a misspelled field name here would otherwise succeed and quietly
105
+ do the wrong thing. It has already earned its keep, catching `token_revoke`
106
+ coercing a string fingerprint to an `int`.
@@ -0,0 +1,91 @@
1
+ # aegisdb — Python client
2
+
3
+ The client for [AegisDB](https://github.com/d4n-larsson/aegisdb)'s
4
+ newline-delimited JSON protocol. **No dependencies** — AegisDB is a single
5
+ dependency-free binary and its wire protocol is one JSON object per line over
6
+ TCP, so the client that talks to it has no business dragging in a tree.
7
+
8
+ ```bash
9
+ pip install aegisdb
10
+ ```
11
+
12
+ ```python
13
+ from aegisdb import AegisClient, NotFound
14
+
15
+ with AegisClient(host="127.0.0.1", port=9470, token="…") as db:
16
+ rec = db.insert("prefers dark mode", type="semantic", tags=["user"])
17
+ print(db.search(query="dark mode", top_k=5)["records"])
18
+ try:
19
+ db.get(999)
20
+ except NotFound as exc:
21
+ print(exc.code, exc.message)
22
+ ```
23
+
24
+ Every wire operation has a method — `insert` / `insert_many`, `get`, `history`,
25
+ `update`, `delete`, `search`, `count`, `consolidate`, `forget`, `export`,
26
+ `purge`, `promote`, `relate`, `traverse`, `conflicts`, `ping`, `stats`,
27
+ `snapshot`, and the token admin trio. Each accepts only the fields the server
28
+ actually reads (the list was taken from the dispatcher, not from prose), plus
29
+ `**extra` as the escape hatch for a field a newer server understands.
30
+
31
+ ## What it does that a bare socket doesn't
32
+
33
+ **Errors are exceptions, one class per wire code.** `NotFound`, `Forbidden`,
34
+ `NotReady`, `RateLimited`, `MemoryLimit`, and the rest — all under
35
+ `AegisRequestError`, which carries `.code` and `.message` verbatim so a code
36
+ this client predates still arrives catchable rather than as a string you compare
37
+ by hand. `AegisUnavailable` is deliberately *not* one of them: a refusal means
38
+ the server did not act, while an unanswered request says nothing either way, and
39
+ that difference is what you reason about when deciding whether to retry.
40
+
41
+ **One connection, reused.** The server supports pipelining and this client
42
+ deliberately does not use it: one line out, one line back, so a response is
43
+ never mistaken for the tail of another. A reused connection that fails with no
44
+ response received is retried once on a fresh one, because the server reaps
45
+ connections idle past `--idle-timeout-sec` and that is exactly what a pause
46
+ between calls looks like.
47
+
48
+ That retry is safe for the case it exists for — a reaped connection never
49
+ delivered the request. It is not safe in general: if the server received the
50
+ request and the answer was lost, a retried `insert` writes a second record.
51
+ Pass `retry_stale=False` where that matters more than the convenience, or
52
+ `reuse=False` for a fresh connection per request.
53
+
54
+ **Not thread-safe.** A client owns one socket. Use one per thread, or
55
+ `reuse=False`.
56
+
57
+ **An unspecified argument is omitted, not defaulted.** `None` means "not
58
+ specified", so the *server's* default applies rather than a copy of it kept
59
+ here — two copies drift, and the client's would silently win. Falsy values are
60
+ not treated as absent: `limit=0` is the `conflicts` count-without-listing probe,
61
+ and `subsume=False` means something.
62
+
63
+ ## `agent_id` does not scope everything
64
+
65
+ `AegisClient(agent_id="…")` is applied to every request that does not name its
66
+ own, mirroring how the server scopes reads and writes. But `consolidate`,
67
+ `forget`, `update`, `delete`, `relate` and `promote` are scoped by the
68
+ **token's** namespace and ignore `agent_id` entirely — so with authentication
69
+ off they act across the whole server whatever you set it to. That asymmetry is
70
+ the server's, not this client's; the affected methods say so in their
71
+ docstrings.
72
+
73
+ ## Version
74
+
75
+ Published from the same `git tag` as the server and the Claude Code
76
+ integration, so `aegisdb`, `aegisdb-mcp` and the server binary all carry the
77
+ same version.
78
+
79
+ ## Tests
80
+
81
+ ```bash
82
+ python3 -m unittest discover -s tests # from clients/python/
83
+ make sdk-test # from the repo root
84
+ ```
85
+
86
+ `test_protocol.py` runs against a fake server and needs nothing. `test_live.py`
87
+ exercises **every** method against a real `build/aegisdb` and skips when it is
88
+ not built — which is the point: the server ignores request fields it does not
89
+ recognise, so a misspelled field name here would otherwise succeed and quietly
90
+ do the wrong thing. It has already earned its keep, catching `token_revoke`
91
+ coercing a string fingerprint to an `int`.
@@ -0,0 +1,24 @@
1
+ """AegisDB — a client for the newline-delimited JSON protocol.
2
+
3
+ from aegisdb import AegisClient
4
+
5
+ with AegisClient(host="127.0.0.1", port=9470) as db:
6
+ rec = db.insert("prefers dark mode", type="semantic", tags=["user"])
7
+ hits = db.search(query="dark mode", top_k=5)
8
+
9
+ Standard library only. See https://github.com/d4n-larsson/aegisdb for the
10
+ server and the full wire-protocol reference.
11
+ """
12
+ from .client import DEFAULT_HOST, DEFAULT_PORT, AegisClient
13
+ from .errors import (AegisError, AegisRequestError, AegisUnavailable,
14
+ Forbidden, Immutable, InternalError, InvalidRequest,
15
+ MemoryLimit, NotFound, NotReady, PayloadTooLarge,
16
+ QuotaExceeded, RateLimited, ReadOnly, Unauthorized)
17
+
18
+ __all__ = [
19
+ "AegisClient", "DEFAULT_HOST", "DEFAULT_PORT",
20
+ "AegisError", "AegisUnavailable", "AegisRequestError",
21
+ "InvalidRequest", "NotFound", "PayloadTooLarge", "Immutable", "NotReady",
22
+ "Unauthorized", "Forbidden", "QuotaExceeded", "RateLimited", "ReadOnly",
23
+ "MemoryLimit", "InternalError",
24
+ ]