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 +106 -0
- aegisdb-0.8.0/README.md +91 -0
- aegisdb-0.8.0/aegisdb/__init__.py +24 -0
- aegisdb-0.8.0/aegisdb/client.py +496 -0
- aegisdb-0.8.0/aegisdb/errors.py +142 -0
- aegisdb-0.8.0/aegisdb.egg-info/PKG-INFO +106 -0
- aegisdb-0.8.0/aegisdb.egg-info/SOURCES.txt +12 -0
- aegisdb-0.8.0/aegisdb.egg-info/dependency_links.txt +1 -0
- aegisdb-0.8.0/aegisdb.egg-info/requires.txt +3 -0
- aegisdb-0.8.0/aegisdb.egg-info/top_level.txt +1 -0
- aegisdb-0.8.0/pyproject.toml +37 -0
- aegisdb-0.8.0/setup.cfg +4 -0
- aegisdb-0.8.0/tests/test_live.py +395 -0
- aegisdb-0.8.0/tests/test_protocol.py +293 -0
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`.
|
aegisdb-0.8.0/README.md
ADDED
|
@@ -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
|
+
]
|