nxd 0.1.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.
nxd-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nexplora Labs
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.
nxd-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: nxd
3
+ Version: 0.1.0
4
+ Summary: Encrypted compute layer for AI agents
5
+ Author: Nexplora Labs
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Nexploraai/nxd
8
+ Project-URL: Repository, https://github.com/Nexploraai/nxd
9
+ Project-URL: Issues, https://github.com/Nexploraai/nxd/issues
10
+ Keywords: fhe,encryption,ai-agents,privacy,homomorphic-encryption
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Topic :: Security :: Cryptography
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: <3.12,>=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: concrete-ml==1.9.0
22
+ Requires-Dist: cryptography>=42.0.0
23
+ Requires-Dist: numpy>=1.26.0
24
+ Requires-Dist: pandas>=2.0.0
25
+ Requires-Dist: scikit-learn>=1.5.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest; extra == "dev"
28
+ Requires-Dist: build; extra == "dev"
29
+ Requires-Dist: twine; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # NXD
33
+
34
+ NXD is an encrypted compute layer for AI agents. It wraps fully homomorphic encryption, credential vaulting, and privacy primitives behind a single Python import — so developers can run agents on sensitive data without exposing client records, credentials, or proprietary code to models, clouds, or MCP servers.
35
+
36
+ ## Three guarantees
37
+
38
+ 1. **The agent works fully** — capability unchanged; scores, matches, charges, and aggregates complete normally.
39
+ 2. **The agent sees nothing** — sensitive values stay encrypted; agents handle opaque tokens and references only.
40
+ 3. **The operator holds the keys** — keys stay local, auditable, and revocable.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ pip install nxd
46
+ ```
47
+
48
+ Requires Python 3.10 or 3.11 (Concrete ML FHE dependency).
49
+
50
+ ## Quick start
51
+
52
+ ```python
53
+ import nxd
54
+
55
+ # FHE compute on encrypted data
56
+ results = nxd.score(model, clients)
57
+ matched = nxd.match(model, record_a, record_b)
58
+ average = nxd.aggregate(model, records)
59
+
60
+ # Credentials — agent never sees plaintext keys
61
+ vault = nxd.Vault(agent_id="billing-agent")
62
+ vault.store("stripe_key", "sk_live_xxxx")
63
+ result = vault.use("stripe_key", stripe_charge_fn)
64
+ vault.audit_log()
65
+
66
+ # Agent-to-agent encrypted context
67
+ handoff = nxd.Handoff()
68
+ token = handoff.pack(clients)
69
+ scores = nxd.receive(model, token, handoff)
70
+
71
+ # Code and text privacy before any AI call
72
+ protected = nxd.shield(source_code)
73
+ original = nxd.unshield(protected)
74
+
75
+ # Encrypted search, identity, tokenization, PII redaction
76
+ index = nxd.build_index(records)
77
+ token, hits = nxd.search(index, "diabetes")
78
+ nxd.register("user_123", "credential")
79
+ nxd.verify("user_123", candidate)
80
+ safe = nxd.redact("Patient John Smith, SSN 432-12-6789")
81
+ token = nxd.tokenize("4532-1234-5678-9010")
82
+
83
+ # Documents, channels, state, signatures
84
+ nxd.seal("contract.pdf")
85
+ ch = nxd.channel("agent-a", "agent-b")
86
+ nxd.checkpoint.save("agent-123", state)
87
+ nxd.sign("agent-a", "approve payment")
88
+
89
+ # Privacy analytics, key control, audit
90
+ nxd.blur(47230.0, sensitivity=1000, epsilon=1.0)
91
+ shares = nxd.split("master_key", n=5, m=3)
92
+ locked = nxd.bind(data, recipient="agent-compliance-7")
93
+ nxd.audit.verify()
94
+ ```
95
+
96
+ ## Benchmarks (MacBook Air, Python 3.11, Concrete ML 1.9.0)
97
+
98
+ | Operation | Latency | Notes |
99
+ |-----------|---------|-------|
100
+ | FHE score (1 record) | ~183 ms | First-call cold start |
101
+ | FHE score (1k records, parallel) | **1.6 s** | 8 cores, ~1.6 ms/record |
102
+ | FHE match (single pair) | **352 ms** | Cross-system comparison |
103
+ | FHE aggregate (1k records, parallel) | **1.8 s** | ~0.009% quantization error |
104
+ | Credential vault use | <1 ms | Decrypt in memory only |
105
+ | Proof suite | **85/85 passed** | `python3 prove.py` |
106
+
107
+ ## Development
108
+
109
+ ```bash
110
+ git clone https://github.com/Nexploraai/nxd
111
+ cd nxd
112
+ pip install -e ".[dev]"
113
+ python3 prove.py
114
+ python3 agent.py
115
+ python3 demo.py
116
+ ```
117
+
118
+ ## License
119
+
120
+ MIT — see [LICENSE](LICENSE).
nxd-0.1.0/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # NXD
2
+
3
+ NXD is an encrypted compute layer for AI agents. It wraps fully homomorphic encryption, credential vaulting, and privacy primitives behind a single Python import — so developers can run agents on sensitive data without exposing client records, credentials, or proprietary code to models, clouds, or MCP servers.
4
+
5
+ ## Three guarantees
6
+
7
+ 1. **The agent works fully** — capability unchanged; scores, matches, charges, and aggregates complete normally.
8
+ 2. **The agent sees nothing** — sensitive values stay encrypted; agents handle opaque tokens and references only.
9
+ 3. **The operator holds the keys** — keys stay local, auditable, and revocable.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install nxd
15
+ ```
16
+
17
+ Requires Python 3.10 or 3.11 (Concrete ML FHE dependency).
18
+
19
+ ## Quick start
20
+
21
+ ```python
22
+ import nxd
23
+
24
+ # FHE compute on encrypted data
25
+ results = nxd.score(model, clients)
26
+ matched = nxd.match(model, record_a, record_b)
27
+ average = nxd.aggregate(model, records)
28
+
29
+ # Credentials — agent never sees plaintext keys
30
+ vault = nxd.Vault(agent_id="billing-agent")
31
+ vault.store("stripe_key", "sk_live_xxxx")
32
+ result = vault.use("stripe_key", stripe_charge_fn)
33
+ vault.audit_log()
34
+
35
+ # Agent-to-agent encrypted context
36
+ handoff = nxd.Handoff()
37
+ token = handoff.pack(clients)
38
+ scores = nxd.receive(model, token, handoff)
39
+
40
+ # Code and text privacy before any AI call
41
+ protected = nxd.shield(source_code)
42
+ original = nxd.unshield(protected)
43
+
44
+ # Encrypted search, identity, tokenization, PII redaction
45
+ index = nxd.build_index(records)
46
+ token, hits = nxd.search(index, "diabetes")
47
+ nxd.register("user_123", "credential")
48
+ nxd.verify("user_123", candidate)
49
+ safe = nxd.redact("Patient John Smith, SSN 432-12-6789")
50
+ token = nxd.tokenize("4532-1234-5678-9010")
51
+
52
+ # Documents, channels, state, signatures
53
+ nxd.seal("contract.pdf")
54
+ ch = nxd.channel("agent-a", "agent-b")
55
+ nxd.checkpoint.save("agent-123", state)
56
+ nxd.sign("agent-a", "approve payment")
57
+
58
+ # Privacy analytics, key control, audit
59
+ nxd.blur(47230.0, sensitivity=1000, epsilon=1.0)
60
+ shares = nxd.split("master_key", n=5, m=3)
61
+ locked = nxd.bind(data, recipient="agent-compliance-7")
62
+ nxd.audit.verify()
63
+ ```
64
+
65
+ ## Benchmarks (MacBook Air, Python 3.11, Concrete ML 1.9.0)
66
+
67
+ | Operation | Latency | Notes |
68
+ |-----------|---------|-------|
69
+ | FHE score (1 record) | ~183 ms | First-call cold start |
70
+ | FHE score (1k records, parallel) | **1.6 s** | 8 cores, ~1.6 ms/record |
71
+ | FHE match (single pair) | **352 ms** | Cross-system comparison |
72
+ | FHE aggregate (1k records, parallel) | **1.8 s** | ~0.009% quantization error |
73
+ | Credential vault use | <1 ms | Decrypt in memory only |
74
+ | Proof suite | **85/85 passed** | `python3 prove.py` |
75
+
76
+ ## Development
77
+
78
+ ```bash
79
+ git clone https://github.com/Nexploraai/nxd
80
+ cd nxd
81
+ pip install -e ".[dev]"
82
+ python3 prove.py
83
+ python3 agent.py
84
+ python3 demo.py
85
+ ```
86
+
87
+ ## License
88
+
89
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,65 @@
1
+ """
2
+ NXD — encrypted agent operating system.
3
+
4
+ Three guarantees:
5
+ 1. The agent works fully
6
+ 2. The agent sees nothing
7
+ 3. The operator holds the keys
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Literal
13
+
14
+ from nxd.audit import export as audit_export
15
+ from nxd.audit import log as audit_log
16
+ from nxd.audit import verify as audit_verify
17
+ from nxd.bind import BindingError, bind, open
18
+ from nxd.blur import blur, blur_dataset, blur_guide, blur_report
19
+ from nxd.channel import Channel, ChannelClosedError, channel
20
+ from nxd.checkpoint import checkpoint
21
+ from nxd.fhe import aggregate, match, score
22
+ from nxd.handoff import Handoff
23
+ from nxd.redact import deredact, redact
24
+ from nxd.search import SearchIndex, build_index, retrieve, search
25
+ from nxd.seal import seal, seal_text, unseal, unseal_text
26
+ from nxd.shield import send_to_model, shield, unshield
27
+ from nxd.sign import sign, sign_record, verify_record, verify_signature
28
+ from nxd.split import InsufficientSharesError, distribute, reconstruct_strict, split
29
+ from nxd.tokenize import detokenize, tokenize, tokenize_record
30
+ from nxd.vault import Vault
31
+ from nxd.verify import LockedError, register, unlock, verify
32
+
33
+ Mode = Literal["auto", "fast", "secure"]
34
+
35
+ __all__ = [
36
+ "score", "match", "aggregate", "receive",
37
+ "Vault", "Handoff",
38
+ "shield", "unshield", "send_to_model",
39
+ "build_index", "search", "retrieve", "SearchIndex",
40
+ "register", "verify", "unlock", "LockedError",
41
+ "tokenize", "detokenize", "tokenize_record",
42
+ "redact", "deredact",
43
+ "seal", "unseal", "seal_text", "unseal_text",
44
+ "channel", "Channel", "ChannelClosedError",
45
+ "checkpoint",
46
+ "sign", "verify_signature", "sign_record", "verify_record",
47
+ "blur", "blur_dataset", "blur_report", "blur_guide",
48
+ "split", "reconstruct_strict", "distribute", "InsufficientSharesError",
49
+ "bind", "open", "BindingError",
50
+ "audit_log", "audit_verify", "audit_export",
51
+ "Mode",
52
+ ]
53
+
54
+
55
+ def receive(model: Any, token: str, handoff: Handoff, mode: Mode = "auto") -> list[int]:
56
+ """Agent B scores encrypted handoff context."""
57
+ context = handoff.unpack(token)
58
+ clients = context if isinstance(context, list) else [context]
59
+ return score(model, clients, mode=mode)
60
+
61
+
62
+ class audit:
63
+ log = staticmethod(audit_log)
64
+ verify = staticmethod(audit_verify)
65
+ export = staticmethod(audit_export)
@@ -0,0 +1,46 @@
1
+ """Shared cryptography utilities for NXD."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import os
7
+ from pathlib import Path
8
+
9
+ from cryptography.fernet import Fernet
10
+
11
+ DEFAULT_DATA_DIR = Path.home() / ".nxd"
12
+ MASTER_KEY_FILE = "master.key"
13
+
14
+
15
+ def data_dir() -> Path:
16
+ import os
17
+ path = Path(os.environ.get("NXD_DATA_DIR", DEFAULT_DATA_DIR))
18
+ path.mkdir(parents=True, exist_ok=True)
19
+ return path
20
+
21
+
22
+ def master_key_path() -> Path:
23
+ return data_dir() / MASTER_KEY_FILE
24
+
25
+
26
+ def load_fernet(key_path: Path | None = None) -> Fernet:
27
+ path = key_path or master_key_path()
28
+ if path.exists():
29
+ key = path.read_bytes()
30
+ else:
31
+ key = Fernet.generate_key()
32
+ path.write_bytes(key)
33
+ os.chmod(path, 0o600)
34
+ return Fernet(key)
35
+
36
+
37
+ def load_hmac_key() -> bytes:
38
+ return hashlib.sha256(master_key_path().read_bytes()).digest()
39
+
40
+
41
+ def encrypt_bytes(data: bytes, fernet: Fernet | None = None) -> bytes:
42
+ return (fernet or load_fernet()).encrypt(data)
43
+
44
+
45
+ def decrypt_bytes(token: bytes, fernet: Fernet | None = None) -> bytes:
46
+ return (fernet or load_fernet()).decrypt(token)
nxd-0.1.0/nxd/audit.py ADDED
@@ -0,0 +1,86 @@
1
+ """Tamper-proof audit chain for all NXD operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import time
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from nxd._crypto import data_dir
12
+
13
+ AUDIT_FILE = "audit_chain.jsonl"
14
+
15
+
16
+ class AuditChain:
17
+ def __init__(self, path: Path | None = None):
18
+ self.path = path or (data_dir() / AUDIT_FILE)
19
+ self.path.parent.mkdir(parents=True, exist_ok=True)
20
+ if not self.path.exists():
21
+ self.path.write_text("")
22
+
23
+ def _last_hash(self) -> str:
24
+ lines = [l for l in self.path.read_text().splitlines() if l.strip()]
25
+ if not lines:
26
+ return "genesis"
27
+ return json.loads(lines[-1])["hash"]
28
+
29
+ def log(self, operation: str, **meta: Any) -> dict:
30
+ prev = self._last_hash()
31
+ entry = {
32
+ "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
33
+ "operation": operation,
34
+ "meta": meta,
35
+ "prev_hash": prev,
36
+ }
37
+ payload = json.dumps(entry, sort_keys=True)
38
+ entry["hash"] = hashlib.sha256((prev + payload).encode()).hexdigest()
39
+ with self.path.open("a") as f:
40
+ f.write(json.dumps(entry) + "\n")
41
+ return entry
42
+
43
+ def entries(self) -> list[dict]:
44
+ return [json.loads(l) for l in self.path.read_text().splitlines() if l.strip()]
45
+
46
+ def verify(self) -> tuple[bool, int | None]:
47
+ lines = self.entries()
48
+ prev = "genesis"
49
+ for i, entry in enumerate(lines):
50
+ stored = entry.pop("hash")
51
+ payload = json.dumps(entry, sort_keys=True)
52
+ expected = hashlib.sha256((prev + payload).encode()).hexdigest()
53
+ if stored != expected:
54
+ entry["hash"] = stored
55
+ return False, i
56
+ prev = stored
57
+ entry["hash"] = stored
58
+ return True, None
59
+
60
+ def export(self, out_path: str | Path) -> Path:
61
+ out = Path(out_path)
62
+ report = {"chain_valid": self.verify()[0], "entries": self.entries()}
63
+ out.write_text(json.dumps(report, indent=2))
64
+ return out
65
+
66
+
67
+ _audit = AuditChain()
68
+
69
+
70
+ def log(operation: str, **meta: Any) -> dict:
71
+ return _audit.log(operation, **meta)
72
+
73
+
74
+ def verify() -> tuple[bool, int | None]:
75
+ return _audit.verify()
76
+
77
+
78
+ def export(out_path: str | Path) -> Path:
79
+ return _audit.export(out_path)
80
+
81
+
82
+ def reset_for_tests(path: Path) -> None:
83
+ global _audit
84
+ path.parent.mkdir(parents=True, exist_ok=True)
85
+ path.write_text("")
86
+ _audit = AuditChain(path)
nxd-0.1.0/nxd/bind.py ADDED
@@ -0,0 +1,50 @@
1
+ """Data binding — encrypt locked to specific recipient and purpose."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import json
7
+
8
+ from cryptography.fernet import Fernet
9
+ from cryptography.hazmat.primitives import hashes
10
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
11
+
12
+ from nxd._crypto import master_key_path
13
+
14
+
15
+ class BindingError(Exception):
16
+ pass
17
+
18
+
19
+ def _derive_key(recipient: str, purpose: str = "") -> Fernet:
20
+ seed = master_key_path().read_bytes()
21
+ derived = HKDF(
22
+ algorithm=hashes.SHA256(),
23
+ length=32,
24
+ salt=b"nxd-bind",
25
+ info=f"{recipient}:{purpose}".encode(),
26
+ ).derive(seed)
27
+ key = base64.urlsafe_b64encode(derived)
28
+ return Fernet(key)
29
+
30
+
31
+ def bind(data: str, recipient: str, purpose: str = "") -> str:
32
+ from nxd import audit
33
+ payload = json.dumps({"data": data, "recipient": recipient, "purpose": purpose})
34
+ token = _derive_key(recipient, purpose).encrypt(payload.encode()).decode()
35
+ audit.log("bind", recipient=recipient, purpose=purpose)
36
+ return token
37
+
38
+
39
+ def open(locked: str, caller: str, purpose: str = "") -> str:
40
+ from nxd import audit
41
+ try:
42
+ payload = json.loads(_derive_key(caller, purpose).decrypt(locked.encode()).decode())
43
+ except Exception as e:
44
+ raise BindingError(f"Access denied for {caller}") from e
45
+ if payload["recipient"] != caller:
46
+ raise BindingError(f"Bound to {payload['recipient']}, not {caller}")
47
+ if purpose and payload.get("purpose") and payload["purpose"] != purpose:
48
+ raise BindingError(f"Bound to purpose '{payload['purpose']}', not '{purpose}'")
49
+ audit.log("open", caller=caller, purpose=purpose)
50
+ return payload["data"]
nxd-0.1.0/nxd/blur.py ADDED
@@ -0,0 +1,41 @@
1
+ """Differential privacy — blur aggregates for safe sharing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+
7
+
8
+ def blur(value: float, sensitivity: float = 1.0, epsilon: float = 1.0) -> float:
9
+ from nxd import audit
10
+ scale = sensitivity / epsilon
11
+ noise = np.random.laplace(0, scale)
12
+ result = float(value + noise)
13
+ audit.log("blur", epsilon=epsilon)
14
+ return result
15
+
16
+
17
+ def blur_dataset(values: list[float], sensitivity: float = 1.0, epsilon: float = 1.0) -> list[float]:
18
+ from nxd import audit
19
+ result = [blur(v, sensitivity, epsilon) for v in values]
20
+ audit.log("blur_dataset", count=len(values))
21
+ return result
22
+
23
+
24
+ def blur_report(records: list[dict], fields: list[str], epsilon: float = 1.0) -> dict:
25
+ from nxd import audit
26
+ report = {}
27
+ for field in fields:
28
+ vals = [float(r[field]) for r in records if field in r]
29
+ if vals:
30
+ noisy = blur_dataset(vals, sensitivity=max(vals) * 0.1, epsilon=epsilon)
31
+ report[field] = {"mean": float(np.mean(noisy)), "std": float(np.std(noisy))}
32
+ audit.log("blur_report", fields=fields)
33
+ return report
34
+
35
+
36
+ def blur_guide() -> str:
37
+ return (
38
+ "epsilon=0.1: very strong privacy, less accuracy\n"
39
+ "epsilon=1.0: balanced\n"
40
+ "epsilon=10.0: weak privacy, high accuracy"
41
+ )
@@ -0,0 +1,63 @@
1
+ """Encrypted channels — E2E messaging between agents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import json
7
+ import time
8
+
9
+ from cryptography.fernet import Fernet
10
+ from cryptography.hazmat.primitives import hashes
11
+ from cryptography.hazmat.primitives.kdf.hkdf import HKDF
12
+
13
+ from nxd._crypto import master_key_path
14
+
15
+
16
+ class ChannelClosedError(Exception):
17
+ pass
18
+
19
+
20
+ def _session_fernet(sender: str, receiver: str) -> Fernet:
21
+ seed = master_key_path().read_bytes()
22
+ derived = HKDF(
23
+ algorithm=hashes.SHA256(),
24
+ length=32,
25
+ salt=b"nxd-channel",
26
+ info=f"{sender}:{receiver}".encode(),
27
+ ).derive(seed)
28
+ key = base64.urlsafe_b64encode(derived)
29
+ return Fernet(key)
30
+
31
+
32
+ class Channel:
33
+ def __init__(self, sender: str, receiver: str):
34
+ self.sender = sender
35
+ self.receiver = receiver
36
+ self._closed = False
37
+ self._fernet = _session_fernet(sender, receiver)
38
+
39
+ def send(self, message: str) -> str:
40
+ from nxd import audit
41
+ if self._closed:
42
+ raise ChannelClosedError("Channel is closed")
43
+ packet = json.dumps({"ts": time.time(), "msg": message}).encode()
44
+ token = self._fernet.encrypt(packet).decode()
45
+ audit.log("channel_send", sender=self.sender, receiver=self.receiver)
46
+ return token
47
+
48
+ def receive(self, packet: str) -> str:
49
+ from nxd import audit
50
+ if self._closed:
51
+ raise ChannelClosedError("Channel is closed")
52
+ payload = json.loads(self._fernet.decrypt(packet.encode()).decode())
53
+ audit.log("channel_receive", receiver=self.receiver)
54
+ return payload["msg"]
55
+
56
+ def close(self) -> None:
57
+ from nxd import audit
58
+ self._closed = True
59
+ audit.log("channel_close", sender=self.sender, receiver=self.receiver)
60
+
61
+
62
+ def channel(sender: str, receiver: str) -> Channel:
63
+ return Channel(sender, receiver)
@@ -0,0 +1,75 @@
1
+ """Agent state persistence — encrypted checkpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import gzip
6
+ import json
7
+ import time
8
+ from pathlib import Path
9
+
10
+ from nxd._crypto import data_dir, load_fernet
11
+
12
+
13
+ class CheckpointStore:
14
+ def __init__(self, path: Path | None = None):
15
+ self.dir = path or (data_dir() / "checkpoints")
16
+ self.dir.mkdir(parents=True, exist_ok=True)
17
+
18
+ def _file(self, agent_id: str) -> Path:
19
+ return self.dir / f"{agent_id}.nxd"
20
+
21
+ def save(self, agent_id: str, state: dict) -> Path:
22
+ from nxd import audit
23
+ f = load_fernet()
24
+ meta = {"agent_id": agent_id, "timestamp": time.time(), "keys": list(state.keys())}
25
+ payload = gzip.compress(json.dumps({"meta": meta, "state": state}).encode())
26
+ out = self._file(agent_id)
27
+ out.write_bytes(f.encrypt(payload))
28
+ audit.log("checkpoint_save", agent_id=agent_id)
29
+ return out
30
+
31
+ def load(self, agent_id: str) -> dict:
32
+ from nxd import audit
33
+ out = self._file(agent_id)
34
+ if not out.exists():
35
+ raise FileNotFoundError(f"No checkpoint for {agent_id}")
36
+ f = load_fernet()
37
+ payload = gzip.decompress(f.decrypt(out.read_bytes()))
38
+ data = json.loads(payload.decode())
39
+ audit.log("checkpoint_load", agent_id=agent_id)
40
+ return data["state"]
41
+
42
+ def list_checkpoints(self) -> list[str]:
43
+ entries = []
44
+ for p in self.dir.glob("*.nxd"):
45
+ try:
46
+ f = load_fernet()
47
+ payload = gzip.decompress(f.decrypt(p.read_bytes()))
48
+ meta = json.loads(payload.decode())["meta"]
49
+ ts = time.strftime("%Y-%m-%d %H:%M", time.localtime(meta["timestamp"]))
50
+ entries.append(f"{meta['agent_id']} | {ts} | {len(meta['keys'])} keys")
51
+ except Exception:
52
+ entries.append(p.stem)
53
+ return entries
54
+
55
+ def purge(self, agent_id: str) -> None:
56
+ from nxd import audit
57
+ out = self._file(agent_id)
58
+ if out.exists():
59
+ out.unlink()
60
+ audit.log("checkpoint_purge", agent_id=agent_id)
61
+
62
+
63
+ _store = CheckpointStore()
64
+
65
+
66
+ class checkpoint:
67
+ save = staticmethod(lambda agent_id, state: _store.save(agent_id, state))
68
+ load = staticmethod(lambda agent_id: _store.load(agent_id))
69
+ list = staticmethod(lambda: _store.list_checkpoints())
70
+ purge = staticmethod(lambda agent_id: _store.purge(agent_id))
71
+
72
+
73
+ def reset_for_tests(path: Path) -> None:
74
+ global _store
75
+ _store = CheckpointStore(path)