edbeeching HF Staff commited on
Commit
d1d8733
·
verified ·
1 Parent(s): 90c4556

Release portable agent memory client 0.1.0

Browse files
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ .venv/
2
+ __pycache__/
3
+ .pytest_cache/
4
+ .agent-memory/
5
+ .test-work/
6
+ *.pyc
7
+ dist/
8
+
README.md CHANGED
@@ -25,6 +25,8 @@ python3 -m venv .venv
25
  .venv/bin/python memory-use/scripts/memory.py --help
26
  ```
27
 
 
 
28
  With an existing local HF login or `HF_TOKEN`, run from the project you want to map:
29
 
30
  ```bash
 
25
  .venv/bin/python memory-use/scripts/memory.py --help
26
  ```
27
 
28
+ The automated suite uses local fixtures and never contacts HF. An explicit live test is available with `.venv/bin/python tests/live_smoke.py --bucket namespace/name`; it writes synthetic notes under `memory-client-validation` and publishes a clearly labelled test skill revision to that existing private bucket.
29
+
30
  With an existing local HF login or `HF_TOKEN`, run from the project you want to map:
31
 
32
  ```bash
scripts/deploy_space.py CHANGED
@@ -8,8 +8,8 @@ from huggingface_hub import CommitOperationAdd, HfApi
8
 
9
  def deploy(space):
10
  root = Path(__file__).resolve().parents[1]
11
- names = ["README.md", "index.html", "style.css", "bootstrap.md", "requirements.txt",
12
- "docs/agent-memory-design.md", "scripts/deploy_space.py"]
13
  names += [p.relative_to(root).as_posix() for p in (root / "memory-use").rglob("*")
14
  if p.is_file() and "__pycache__" not in p.parts and p.suffix != ".pyc"]
15
  api = HfApi()
 
8
 
9
  def deploy(space):
10
  root = Path(__file__).resolve().parents[1]
11
+ names = ["README.md", "index.html", "style.css", "bootstrap.md", "requirements.txt", ".gitignore",
12
+ "docs/agent-memory-design.md", "scripts/deploy_space.py", "tests/test_memory.py", "tests/live_smoke.py"]
13
  names += [p.relative_to(root).as_posix() for p in (root / "memory-use").rglob("*")
14
  if p.is_file() and "__pycache__" not in p.parts and p.suffix != ".pyc"]
15
  api = HfApi()
tests/live_smoke.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Explicit live test: writes synthetic notes and one skill revision to an existing private bucket."""
2
+ import argparse
3
+ import importlib.util
4
+ import json
5
+ from pathlib import Path
6
+ import uuid
7
+
8
+ ROOT = Path(__file__).resolve().parents[1]
9
+ spec = importlib.util.spec_from_file_location("memory", ROOT / "memory-use/scripts/memory.py")
10
+ m = importlib.util.module_from_spec(spec)
11
+ spec.loader.exec_module(m)
12
+
13
+
14
+ def run(bucket, state):
15
+ state.mkdir(parents=True)
16
+ first = m.Memory(state / "machine-one")
17
+ second = m.Memory(state / "machine-two")
18
+ work_a, work_b = state / "checkout-a", state / "different-checkout-b"
19
+ work_a.mkdir()
20
+ work_b.mkdir()
21
+ for client, work, runtime in ((first, work_a, "codex"), (second, work_b, "claude")):
22
+ instructions = client.home / ("AGENTS.md" if runtime == "codex" else "CLAUDE.md")
23
+ instructions.write_text("Preserve this unrelated instruction.\n")
24
+ result = client.bootstrap("memory-client-validation", work, bucket=bucket, runtime=runtime,
25
+ skills_dir=client.home / "skills", instructions=instructions)
26
+ assert result["status"] == "ready", result
27
+ assert instructions.read_text().startswith("Preserve this unrelated instruction.")
28
+ note = first.save("Synthetic portability check: independent clients retrieve the same immutable checkpoint.",
29
+ "Client portability validation", kind="checkpoint", cwd=work_a)
30
+ assert note["status"] == "saved", note
31
+ read = second.read(note["id"], cwd=work_b, fresh=True)
32
+ assert "Synthetic portability check" in read["text"]
33
+ assert second.search("portability", cwd=work_b)["sync"] == "fresh"
34
+ skill = state / "memory-client-smoke"
35
+ skill.mkdir()
36
+ (skill / "SKILL.md").write_text("---\nname: memory-client-smoke\ndescription: Synthetic skill used to verify memory client portability.\n---\nRead references/check.md when validating a memory installation. This skill is test data.\n")
37
+ (skill / "references").mkdir()
38
+ (skill / "references/check.md").write_text("Check that a note written by one client can be read by another.\n")
39
+ published = first.publish(skill, cwd=work_a)
40
+ assert published["status"] == "saved", published
41
+ second.install_shared("memory-client-smoke", published["revision"], "claude", cwd=work_b)
42
+ assert (second.home / "skills/memory-client-smoke/references/check.md").read_text().startswith("Check that")
43
+ return {"status": "passed", "bucket": bucket, "checkpoint_id": note["id"],
44
+ "skill_revision": published["revision"], "isolated_clients": 2,
45
+ "physical_machines": 1, "space_dependency": False, "state": str(state)}
46
+
47
+
48
+ if __name__ == "__main__":
49
+ p = argparse.ArgumentParser(description=__doc__)
50
+ p.add_argument("--bucket", required=True)
51
+ p.add_argument("--state", type=Path, default=ROOT / ".test-work" / ("live-" + uuid.uuid4().hex[:8]))
52
+ args = p.parse_args()
53
+ print(json.dumps(run(args.bucket, args.state), indent=2))
tests/test_memory.py ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.util
2
+ import json
3
+ from pathlib import Path
4
+ import subprocess
5
+ import sys
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from types import SimpleNamespace
8
+
9
+ import pytest
10
+
11
+ ROOT = Path(__file__).resolve().parents[1]
12
+ spec = importlib.util.spec_from_file_location("memory", ROOT / "memory-use/scripts/memory.py")
13
+ m = importlib.util.module_from_spec(spec)
14
+ spec.loader.exec_module(m)
15
+
16
+
17
+ class Bucket:
18
+ """An in-memory fake of bucket operations; never used by the production CLI."""
19
+ def __init__(self):
20
+ self.buckets = {}
21
+ self.calls = []
22
+ self.fail = None
23
+ self.fail_marker = False
24
+ self.unknown_put = False
25
+
26
+ def call(self, op, **kw):
27
+ self.calls.append((op, kw))
28
+ if self.fail:
29
+ raise m.MemoryError("Simulated " + self.fail, self.fail)
30
+ if op == "whoami":
31
+ return {"name": "owner", "orgs": ["team"]}
32
+ if op == "create":
33
+ if kw["bucket"] in self.buckets:
34
+ raise m.MemoryError("Already exists", "conflict")
35
+ self.buckets[kw["bucket"]] = {"private": True, "files": {}}
36
+ return {}
37
+ if op == "core":
38
+ return {"path": str(ROOT / "memory-use")}
39
+ if kw["bucket"] not in self.buckets:
40
+ raise m.MemoryError("Missing", "not-found")
41
+ bucket = self.buckets[kw["bucket"]]
42
+ files = bucket["files"]
43
+ if op == "info":
44
+ return {"id": kw["bucket"], "private": bucket["private"]}
45
+ if op == "put":
46
+ if self.fail_marker and kw["path"].endswith("/SKILL.md"):
47
+ raise m.MemoryError("Interrupted publication", "offline")
48
+ files[kw["path"]] = kw["text"]
49
+ if self.unknown_put:
50
+ raise m.MemoryError("Lost acknowledgment", "offline")
51
+ return {}
52
+ if op == "get":
53
+ if kw["path"] not in files:
54
+ raise m.MemoryError("Missing", "not-found")
55
+ return {"text": files[kw["path"]]}
56
+ if op == "list":
57
+ return [{"path": p, "size": len(t.encode())} for p, t in files.items() if p.startswith(kw["prefix"])]
58
+ raise AssertionError(op)
59
+
60
+
61
+ @pytest.fixture
62
+ def setup(tmp_path):
63
+ store = Bucket()
64
+ client = m.Memory(tmp_path / "state", store)
65
+ work = tmp_path / "project"
66
+ work.mkdir()
67
+ client.provision("project", work)
68
+ return client, store, work
69
+
70
+
71
+ def example_skill(tmp_path, body="Use a release checklist."):
72
+ folder = tmp_path / "release-check"
73
+ folder.mkdir(exist_ok=True)
74
+ (folder / "SKILL.md").write_text("---\nname: release-check\ndescription: Check releases.\n---\n\n" + body)
75
+ (folder / "references").mkdir(exist_ok=True)
76
+ (folder / "references/check.md").write_text("Verify the release artifact.")
77
+ return folder
78
+
79
+
80
+ def setup_runtime(client, tmp_path, store, work):
81
+ instructions = tmp_path / "AGENTS.md"
82
+ instructions.write_text("Keep existing conventions.\n")
83
+ return client.bootstrap("project", work, bucket="owner/agent-memory", skills_dir=tmp_path / "skills", instructions=instructions)
84
+
85
+
86
+ def test_two_machines_share_memories_and_keep_corrections(setup, tmp_path):
87
+ one, store, work = setup
88
+ saved = one.save("Use uv for this project.", "Package tooling", cwd=work)
89
+ two = m.Memory(tmp_path / "second-machine", store)
90
+ other_checkout = tmp_path / "other-checkout"
91
+ other_checkout.mkdir()
92
+ two.provision("project", other_checkout, "owner/agent-memory")
93
+ results = two.search("UV", cwd=other_checkout)
94
+ assert results["sync"] == "fresh" and len(results["results"]) == 1
95
+ assert "Use uv" in two.read(saved["id"], cwd=other_checkout)["text"]
96
+ correction = two.save("Correction: use pip. Supersedes " + saved["id"], "Tooling correction", cwd=other_checkout)
97
+ assert saved["id"] != correction["id"]
98
+ assert len(one.search(cwd=work)["results"]) == 2
99
+
100
+
101
+ def test_unknown_upload_retries_identical_bytes_after_configuration_change(setup):
102
+ client, store, work = setup
103
+ store.unknown_put = True
104
+ result = client.save("Verified result", "Checkpoint", kind="checkpoint", cwd=work)
105
+ assert result["status"] == "pending"
106
+ original = dict(store.buckets["owner/agent-memory"]["files"])
107
+ store.unknown_put = False
108
+ client.provision("other", work, create_bucket="owner/other")
109
+ assert client.retry(result["id"])["status"] == "saved"
110
+ assert store.buckets["owner/agent-memory"]["files"] == original
111
+ assert not store.buckets["owner/other"]["files"]
112
+ assert not client.pending()
113
+
114
+
115
+ def test_save_offline_is_durable_and_retryable(setup):
116
+ client, store, work = setup
117
+ store.fail = "offline"
118
+ result = client.save("Keep this finding", "Finding", cwd=work)
119
+ assert result["status"] == "pending"
120
+ assert client.pending()[0]["text"].endswith("Keep this finding\n")
121
+ restarted = m.Memory(client.home, store)
122
+ store.fail = None
123
+ assert restarted.retry(result["id"])["status"] == "saved"
124
+
125
+
126
+ def test_no_cleanup_before_remote_confirmation(setup, monkeypatch):
127
+ client, store, work = setup
128
+ original = m.write_json
129
+ def crash_on_receipt(path, value):
130
+ if Path(path).name == "last-upload.json":
131
+ raise OSError("Simulated disk failure after upload")
132
+ return original(path, value)
133
+ monkeypatch.setattr(m, "write_json", crash_on_receipt)
134
+ with pytest.raises(OSError):
135
+ client.save("Retain until acknowledged", "Crash case", cwd=work)
136
+ job = client.pending()[0]
137
+ assert len(store.buckets["owner/agent-memory"]["files"]) == 1
138
+ monkeypatch.setattr(m, "write_json", original)
139
+ client.retry(job["id"])
140
+ assert len(store.buckets["owner/agent-memory"]["files"]) == 1
141
+ assert not client.pending()
142
+
143
+
144
+ @pytest.mark.parametrize("failure", ["offline", "auth"])
145
+ def test_cached_search_distinguishes_failed_sync_and_does_not_prune(setup, failure):
146
+ client, store, work = setup
147
+ client.save("Cached evidence", "Evidence", cwd=work)
148
+ client.search(cwd=work)
149
+ store.fail = failure
150
+ result = client.search("evidence", cwd=work)
151
+ assert result["stale"] and len(result["results"]) == 1
152
+ assert result["sync"] == ("auth-error" if failure == "auth" else "offline")
153
+
154
+
155
+ def test_incomplete_listing_and_download_preserve_cache(setup, monkeypatch):
156
+ client, store, work = setup
157
+ old = client.save("Keep cached note", "Old", cwd=work)
158
+ client.search(cwd=work)
159
+ original = store.call
160
+ def interrupted(op, **kw):
161
+ if op == "list":
162
+ raise m.MemoryError("Pagination failed after page one", "offline")
163
+ return original(op, **kw)
164
+ monkeypatch.setattr(store, "call", interrupted)
165
+ assert len(client.search(cwd=work)["results"]) == 1
166
+ monkeypatch.setattr(store, "call", original)
167
+ store.buckets["owner/agent-memory"]["files"].pop(old["path"])
168
+ new = client.save("New note", "New", cwd=work)
169
+ def broken_download(op, **kw):
170
+ if op == "get" and kw["path"] == new["path"]:
171
+ raise m.MemoryError("Download interrupted", "offline")
172
+ return original(op, **kw)
173
+ monkeypatch.setattr(store, "call", broken_download)
174
+ assert client.search(cwd=work)["stale"]
175
+ assert (client.cache("owner/agent-memory") / old["path"]).exists()
176
+ assert not (client.cache("owner/agent-memory") / new["path"]).exists()
177
+ monkeypatch.setattr(store, "call", original)
178
+ assert not client.search(cwd=work)["stale"]
179
+ assert not (client.cache("owner/agent-memory") / old["path"]).exists()
180
+
181
+
182
+ def test_explicit_missing_bucket_never_creates_replacement(tmp_path):
183
+ store = Bucket()
184
+ client = m.Memory(tmp_path / "state", store)
185
+ with pytest.raises(m.MemoryError):
186
+ client.provision("project", tmp_path, "owner/missing")
187
+ assert not any(op == "create" for op, _ in store.calls)
188
+
189
+
190
+ def test_configured_missing_bucket_is_not_replaced(setup):
191
+ client, store, work = setup
192
+ store.buckets.clear()
193
+ store.calls.clear()
194
+ with pytest.raises(m.MemoryError):
195
+ client.provision("project", work)
196
+ assert not any(op == "create" for op, _ in store.calls)
197
+
198
+
199
+ def test_public_default_bucket_is_not_reconfigured(tmp_path):
200
+ store = Bucket()
201
+ store.buckets["owner/agent-memory"] = {"private": False, "files": {}}
202
+ with pytest.raises(m.MemoryError, match="private"):
203
+ m.Memory(tmp_path / "state", store).provision("project", tmp_path)
204
+ assert store.buckets["owner/agent-memory"]["private"] is False
205
+ assert not any(op == "create" for op, _ in store.calls)
206
+
207
+
208
+ def test_longest_project_ancestor_and_directory_boundaries(setup, tmp_path):
209
+ client, _, work = setup
210
+ nested = work / "nested"
211
+ nested.mkdir()
212
+ client.provision("nested", nested, "owner/agent-memory")
213
+ assert client.project(nested / "src")["project"] == "nested"
214
+ assert client.project(work / "other")["project"] == "project"
215
+ with pytest.raises(m.MemoryError, match="no memory project"):
216
+ client.save("Do not route incorrectly", "Unmapped", cwd=tmp_path / "project-other")
217
+
218
+
219
+ def test_bootstrap_repeat_preserves_instructions_and_verification(setup, tmp_path):
220
+ client, store, work = setup
221
+ first = setup_runtime(client, tmp_path, store, work)
222
+ instructions = tmp_path / "AGENTS.md"
223
+ instructions.write_text(instructions.read_text() + "\nMore unrelated instructions.\n")
224
+ second = client.bootstrap("project", work, bucket="owner/agent-memory", skills_dir=tmp_path / "skills", instructions=instructions)
225
+ assert first["verification_id"] == second["verification_id"]
226
+ assert len(store.buckets["owner/agent-memory"]["files"]) == 1
227
+ text = instructions.read_text()
228
+ assert text.startswith("Keep existing conventions.") and text.endswith("More unrelated instructions.\n")
229
+ assert text.count(m.BEGIN) == 1
230
+
231
+
232
+ def test_skill_revisions_partial_publication_and_frozen_retry(setup, tmp_path):
233
+ client, store, work = setup
234
+ folder = example_skill(tmp_path)
235
+ store.fail_marker = True
236
+ first = client.publish(folder, cwd=work)
237
+ assert first["status"] == "pending"
238
+ assert not client.search(skills=True, cwd=work)["results"]
239
+ original_text = (folder / "SKILL.md").read_text()
240
+ (folder / "SKILL.md").write_text(original_text + "\nA subsequent local edit.")
241
+ store.fail_marker = False
242
+ client.retry(first["id"])
243
+ assert store.buckets["owner/agent-memory"]["files"][first["path"] + "SKILL.md"] == original_text
244
+ second = client.publish(folder, cwd=work)
245
+ assert second["revision"] != first["revision"]
246
+ assert len(client.search(skills=True, cwd=work)["results"]) == 2
247
+
248
+
249
+ def test_install_preserves_modified_skill_and_explicit_replacement_backs_up(setup, tmp_path):
250
+ client, store, work = setup
251
+ setup_runtime(client, tmp_path, store, work)
252
+ folder = example_skill(tmp_path)
253
+ published = client.publish(folder, cwd=work)
254
+ client.install_shared("release-check", published["revision"], cwd=work)
255
+ installed = tmp_path / "skills/release-check/references/check.md"
256
+ installed.write_text("Local modifications")
257
+ with pytest.raises(m.MemoryError, match="Existing skill differs"):
258
+ client.install_shared("release-check", published["revision"], cwd=work)
259
+ assert installed.read_text() == "Local modifications"
260
+ result = client.install_shared("release-check", published["revision"], cwd=work, replace=True)
261
+ assert (Path(result["backup"]) / "references/check.md").read_text() == "Local modifications"
262
+ assert "release-check" in (tmp_path / "AGENTS.md").read_text()
263
+
264
+
265
+ @pytest.mark.parametrize("bad", ["../escape", "/absolute", "a/../../escape", "a\\b", "a//b", "./file", "C:/file"])
266
+ def test_unsafe_paths_rejected(bad):
267
+ with pytest.raises(m.MemoryError):
268
+ m.validate_skill({"SKILL.md": "---\nname: example\ndescription: Example\n---\nBody", bad: "bad"})
269
+
270
+
271
+ def test_symlink_publish_and_install_destination_rejected(setup, tmp_path):
272
+ client, store, work = setup
273
+ folder = example_skill(tmp_path)
274
+ (folder / "link").symlink_to(tmp_path / "outside")
275
+ with pytest.raises(m.MemoryError, match="symlink"):
276
+ client.publish(folder, cwd=work)
277
+ (folder / "link").unlink()
278
+ root = tmp_path / "linked-skills"
279
+ root.symlink_to(folder, target_is_directory=True)
280
+ with pytest.raises(m.MemoryError, match="Symlinks"):
281
+ client.install_files(m.skill_files(folder), "release-check", root, {})
282
+
283
+
284
+ def test_core_name_cannot_be_published_or_installed_from_bucket(setup):
285
+ client, _, work = setup
286
+ with pytest.raises(m.MemoryError, match="pinned Space"):
287
+ client.publish(ROOT / "memory-use", cwd=work)
288
+ with pytest.raises(m.MemoryError, match="cannot replace"):
289
+ client.install_shared("memory-use", "abc", cwd=work)
290
+
291
+
292
+ def test_reminder_has_no_network_or_transcript_reads(setup, tmp_path):
293
+ client, store, work = setup
294
+ store.calls.clear()
295
+ payload = {"hook_event_name": "UserPromptSubmit", "cwd": str(work), "transcript_path": "/must/not/read", "prompt": "Do not echo my message"}
296
+ result = m.hook_output(client, payload)
297
+ assert result["hookSpecificOutput"]["hookEventName"] == "UserPromptSubmit"
298
+ assert "Do not echo" not in json.dumps(result)
299
+ assert not store.calls
300
+ assert m.hook_output(client, {**payload, "cwd": str(tmp_path / "unmapped")}) == {}
301
+
302
+
303
+ def test_hook_merge_remove_preserves_unrelated_handlers(setup, tmp_path, monkeypatch):
304
+ client, store, work = setup
305
+ setup_runtime(client, tmp_path, store, work)
306
+ config = tmp_path / "hooks.json"
307
+ unrelated = {"type": "command", "command": "echo unrelated"}
308
+ initial = {"description": "Existing hooks", "hooks": {"SessionStart": [{"matcher": "resume", "hooks": [unrelated]}], "Stop": [{"hooks": [unrelated]}]}}
309
+ config.write_text(json.dumps(initial))
310
+ monkeypatch.setattr(m.subprocess, "run", lambda *a, **k: SimpleNamespace(stdout="codex-cli 0.162.1"))
311
+ m.hooks(client, "install", config)
312
+ m.hooks(client, "install", config)
313
+ value = json.loads(config.read_text())
314
+ assert len(value["hooks"]["SessionStart"]) == 2
315
+ m.hooks(client, "remove", config)
316
+ assert json.loads(config.read_text()) == initial
317
+
318
+
319
+ def test_credentials_rejected_and_errors_do_not_echo_sdk_secrets(setup, monkeypatch):
320
+ client, _, work = setup
321
+ token = "hf_" + "x" * 30
322
+ monkeypatch.setenv("HF_TOKEN", token)
323
+ with pytest.raises(m.MemoryError, match="credential"):
324
+ client.save(token, "Bad note", cwd=work)
325
+ exc = Exception("Authorization: " + token)
326
+ assert token not in str(m.error_from_exception(exc))
327
+
328
+
329
+ def test_cli_json_errors_and_hook_fail_open(tmp_path):
330
+ script = ROOT / "memory-use/scripts/memory.py"
331
+ command = [sys.executable, str(script), "--home", str(tmp_path / "state")]
332
+ result = subprocess.run(command + ["search", "query", "--json"], capture_output=True, text=True, cwd=tmp_path)
333
+ assert result.returncode == 1
334
+ assert json.loads(result.stdout)["code"] == "unmapped"
335
+ hook = subprocess.run(command + ["hook"], input="invalid JSON", capture_output=True, text=True)
336
+ assert hook.returncode == 0
337
+ assert "systemMessage" in json.loads(hook.stdout)
338
+
339
+
340
+ def test_native_network_child_has_hard_deadline(tmp_path, monkeypatch):
341
+ hanging = tmp_path / "hanging.py"
342
+ hanging.write_text("import time\ntime.sleep(10)\n")
343
+ monkeypatch.setattr(m, "__file__", str(hanging))
344
+ with pytest.raises(m.MemoryError, match="deadline"):
345
+ m.HFStore(timeout=0.05).call("whoami")
346
+
347
+
348
+ def test_concurrent_saves_survive_with_distinct_objects(setup):
349
+ client, store, work = setup
350
+ with ThreadPoolExecutor(max_workers=4) as pool:
351
+ futures = [pool.submit(client.save, f"Finding {i}", f"Concurrent {i}", cwd=work) for i in range(12)]
352
+ results = [f.result() for f in futures]
353
+ assert len({r["id"] for r in results}) == 12
354
+ assert all(r["status"] == "saved" for r in results)
355
+ assert len(store.buckets["owner/agent-memory"]["files"]) == 12
356
+ assert not client.pending()
357
+
358
+
359
+ def test_remote_traversal_skill_never_changes_installation(setup, tmp_path):
360
+ client, store, work = setup
361
+ setup_runtime(client, tmp_path, store, work)
362
+ prefix = "skills/hostile/revision/"
363
+ store.buckets["owner/agent-memory"]["files"].update({
364
+ prefix + "SKILL.md": "---\nname: hostile\ndescription: Test\n---\nInstructions",
365
+ prefix + "../../../escaped": "Bad path",
366
+ })
367
+ with pytest.raises(m.MemoryError, match="Unsafe"):
368
+ client.install_shared("hostile", "revision", cwd=work)
369
+ assert not (tmp_path / "skills/hostile").exists()
370
+ assert not (tmp_path / "escaped").exists()
371
+
372
+
373
+ def test_missing_authentication_has_clear_local_message(monkeypatch):
374
+ import huggingface_hub
375
+ monkeypatch.setattr(huggingface_hub, "get_token", lambda: None)
376
+ with pytest.raises(m.MemoryError, match="No HF credentials") as result:
377
+ m.hf_operation({"op": "whoami"})
378
+ assert result.value.code == "auth"
379
+
380
+
381
+ def test_fresh_read_never_silently_falls_back_to_cache(setup):
382
+ client, store, work = setup
383
+ saved = client.save("Cached", "Fresh check", cwd=work)
384
+ client.read(saved["id"], cwd=work)
385
+ store.fail = "offline"
386
+ assert client.read(saved["id"], cwd=work)["stale"]
387
+ with pytest.raises(m.MemoryError):
388
+ client.read(saved["id"], cwd=work, fresh=True)