diff --git a/haiku_rag_slim/haiku/rag/sandbox/sandbox.py b/haiku_rag_slim/haiku/rag/sandbox/sandbox.py index d716fb5f..00c5db72 100644 --- a/haiku_rag_slim/haiku/rag/sandbox/sandbox.py +++ b/haiku_rag_slim/haiku/rag/sandbox/sandbox.py @@ -1,9 +1,9 @@ import asyncio -import atexit -import concurrent.futures import json import os -from collections.abc import Callable +import threading +import weakref +from collections.abc import Callable, Coroutine from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Any, Literal @@ -19,6 +19,8 @@ from haiku.rag.store.models.document_item import PICTURE_REF_PREFIX, DocumentIte if TYPE_CHECKING: from pathlib import PurePosixPath + from haiku.rag.client import HaikuRAG + @dataclass class SandboxResult: @@ -29,13 +31,13 @@ class SandboxResult: success: bool -_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) -atexit.register(_executor.shutdown, wait=False) - - -def _run_async(coro: Any) -> Any: - """Run an async coroutine from a sync context (CallbackFile read).""" - return _executor.submit(asyncio.run, coro).result() +def _stop_loop(loop: asyncio.AbstractEventLoop, thread: threading.Thread) -> None: + """Stop a background loop and join its thread. Safe if already stopped.""" + if loop.is_closed(): + return + loop.call_soon_threadsafe(loop.stop) + thread.join() + loop.close() def _build_toc( @@ -127,6 +129,13 @@ class Sandbox: sandbox = Sandbox(db_path, config, context) result = await sandbox.execute("x = await search('query')") result = await sandbox.execute("print(x[0]['content'])") # x persists + + The virtual filesystem reads run on a dedicated background event loop with a + single read-only connection held for the sandbox's lifetime. Monty's file + callbacks are synchronous and cannot ``await``, so they bridge to that loop + via ``run_coroutine_threadsafe``. Call ``close()`` to release the loop and + connection; if it is not called they are released when the instance is + garbage collected. """ _db_path: Path @@ -139,6 +148,10 @@ class Sandbox: _toc_json_cache: dict[str, str] _repl: MontyRepl | None _vfs: OSAccess | None + _loop: asyncio.AbstractEventLoop | None + _loop_thread: threading.Thread | None + _vfs_rag: "HaikuRAG | None" + _finalizer: weakref.finalize | None def __init__( self, @@ -156,6 +169,60 @@ class Sandbox: self._toc_json_cache = {} self._repl = None self._vfs = None + self._loop = None + self._loop_thread = None + self._vfs_rag = None + self._finalizer = None + + def _ensure_vfs_loop(self) -> None: + """Start the background loop and open the read-only connection once.""" + if self._loop is not None: + return + from haiku.rag.client import HaikuRAG + + loop = asyncio.new_event_loop() + thread = threading.Thread( + target=loop.run_forever, daemon=True, name="sandbox-vfs" + ) + thread.start() + self._loop = loop + self._loop_thread = thread + + async def _open() -> "HaikuRAG": + rag = HaikuRAG(self._db_path, config=self._config, read_only=True) + await rag.__aenter__() + return rag + + self._vfs_rag = self._run_on_loop(_open()) + # GC backstop: capture only the loop + thread, never ``self`` (which + # would pin the instance and prevent collection). + self._finalizer = weakref.finalize(self, _stop_loop, loop, thread) + + def _run_on_loop(self, coro: Coroutine[Any, Any, Any]) -> Any: + """Run a coroutine on the background loop from a synchronous caller.""" + self._ensure_vfs_loop() + assert self._loop is not None + return asyncio.run_coroutine_threadsafe(coro, self._loop).result() + + def close(self) -> None: + """Release the background loop and read-only connection. + + Idempotent and safe to call when no VFS read ever started the loop. + """ + loop, thread, rag = self._loop, self._loop_thread, self._vfs_rag + if loop is None or thread is None: + return + self._loop = None + self._loop_thread = None + self._vfs_rag = None + if self._finalizer is not None: + self._finalizer.detach() + self._finalizer = None + if rag is not None and not loop.is_closed(): + asyncio.run_coroutine_threadsafe( + rag.__aexit__(None, None, None), loop + ).result() + _stop_loop(loop, thread) def _build_external_functions(self) -> dict[str, Any]: """Build async external functions for the Monty interpreter.""" @@ -249,12 +316,12 @@ class Sandbox: return cached async def _fetch() -> list[DocumentItem]: - from haiku.rag.client import HaikuRAG + assert sandbox._vfs_rag is not None + return await sandbox._vfs_rag.document_item_repository.get_all_items( + did + ) - async with HaikuRAG(db_path, config=config, read_only=True) as rag: - return await rag.document_item_repository.get_all_items(did) - - items = _run_async(_fetch()) + items = sandbox._run_on_loop(_fetch()) sandbox._doc_items[did] = items return items @@ -265,17 +332,13 @@ class Sandbox: return cached async def _fetch() -> dict[str, list[str]]: - from haiku.rag.client import HaikuRAG - - async with HaikuRAG(db_path, config=config, read_only=True) as rag: - index = ( - await rag.chunk_repository.get_chunk_ids_by_self_ref_grouped( - [did] - ) - ) + assert sandbox._vfs_rag is not None + index = await sandbox._vfs_rag.chunk_repository.get_chunk_ids_by_self_ref_grouped( + [did] + ) return index.get(did, {}) - chunk_index = _run_async(_fetch()) + chunk_index = sandbox._run_on_loop(_fetch()) sandbox._doc_chunk_index[did] = chunk_index return chunk_index @@ -351,15 +414,13 @@ class Sandbox: ) -> Callable[["PurePosixPath"], str]: def read_content(_path: "PurePosixPath") -> str: async def _fetch() -> str: - from haiku.rag.client import HaikuRAG + assert sandbox._vfs_rag is not None + content = ( + await sandbox._vfs_rag.document_repository.get_content(did) + ) + return content or "" - async with HaikuRAG( - db_path, config=config, read_only=True - ) as rag: - content = await rag.document_repository.get_content(did) - return content or "" - - return _run_async(_fetch()) + return sandbox._run_on_loop(_fetch()) return read_content diff --git a/haiku_rag_slim/haiku/rag/skills/_deps.py b/haiku_rag_slim/haiku/rag/skills/_deps.py index 3fba9463..ed053e22 100644 --- a/haiku_rag_slim/haiku/rag/skills/_deps.py +++ b/haiku_rag_slim/haiku/rag/skills/_deps.py @@ -67,12 +67,16 @@ def make_analysis_lifespan(db_path: Path, config: AppConfig): async with HaikuRAG(db_path, config=config, read_only=True) as rag: deps.rag = rag deps.search_count = 0 - deps.sandbox = Sandbox( + sandbox = Sandbox( db_path=db_path, config=config, context=AnalysisContext(filter=doc_filter), ) + deps.sandbox = sandbox _reset_invocation_state(deps.state) - yield + try: + yield + finally: + sandbox.close() return lifespan diff --git a/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_close_stops_loop_thread.yaml b/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_close_stops_loop_thread.yaml new file mode 100644 index 00000000..e0676a57 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_close_stops_loop_thread.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '85' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Foxes and dogs. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: /BK5OC6vBD0Z6ga8bb+OvEXXYDmH3wo9vXI1PTUx+rw5sQw9S5hkPNAHc7yme/g7d42eO1eEML24syk8YWa9PHzeQj2fKN68IkIBvViNhruTz6u8t/pnO7Fio7zbdQ07zRyRPNyUczvIXr+8jVUPPeQ5Jrsf3li8T4wDvbchDLyQ/is9WS8guy3kcju9A7y8RDJouRDHLrslAg882ma8vJd+SjyhMke9D2CMPNQG9jppJ9O81G71vHL72joWuws83tPjvFCNB710H8I7+j0kPDQmrjxhW5y8VqQJPSjLvbx6f8A9hA8RvOA/YrteRDC8wgwYvAfDAb1H6Im8yfNzvMF1PruT+zS84j0JvFAwiDznllw83hfcvFeKIbwZPAU9AbfMvInb9LoPaxY8NV7avCVfHryoyzY8JtGxPMF1zjmuIKg8XL6hPCQLuju4CyY9FysyPeAqUTxlaSs9FsnXun6WFrw3vp484zkCPJXEXjyscMe7oZJ4vD3rVTupb5o8lTsfvB8qaLz73oy7Do+LPPNbC7zOSYu8OvTpPGJO9rvOykK87D3TvNajMbxkWWK7g+LJOoHiGrzuQjW8LK0uPCp97Dz+8+m8SBOBOnTEhbsJ0As9xKtXPcNGWjyz46o80kYWvFkjTTz9wYe7WTz8PDAABDvexYy8euhKvN3Ln7x+1m+8qWGTPCt35zx/kYG8WzrAPJGGBDyfj6k8kl+UPMFrFzw2Rsq8Qx/Hu3LOzDs/1iq8UazEukXH47vAJak8T/u7vFzkiDy62UG8qJZUO9fLczwtMIY77Rz+PLx/QLuutuE7CMj+PPqK/Tv4Hl88p6G/vHHEzLvd1M07BiNzPKEx8Ts+/Z089IjoPOGcfzxfDr88ApR3O1BBKb3xNdy7VjKGOQfE8Ltpvba8aRGKvGaEbbtPlFu7b5stvFKg8Ds8GB+9Tn2jO9YfQDpZfTq8NKjium65c7xS6MM8YUwMvIAllDyAsQu8qsbbuoMTgTxYRCA7zNwsOsspAr2zQuw7FhjKOzolILwyojC7v8T8uSZyQrxrSrQ7NHyzvIBsET1++XG7LsfxvHc/vbyAxtG7vm6QPJA3eDuwqY+8Xj7rvJMRBzu/Kgc81rRuPJCFdLx6Nu+8ShkUvQk/mDscIGa7CiydvFSUjLx9uBQ9TMPKPP+zjTmGyAs8ozwgvBB8IzsPB5286R+7OzJBhzvzG5M89hAJO/0Ypbom7Y48YNeIu9CHSzupjpm8wyiQvJlxkjyTa+w8kJEIuXngCLz5+Na7tp8HvWCEsrzPC2G81ONPPAIThzs9CPC815LFO15NPjuMuAg7o/LEvIGvcLz+aye7FWg0PBTtKbt+JoW8akZbvPLAaLz5psK7CnWAvDSoqzzyETK8suL5O1m9jTugQYE77CAEO+DmYLx8/4I8pyxgvAO5lzyfyX+8EdAwPVxZuztqY/A8DB/IO8oPTLueIUQ6XkMDvE2LeLyBUek7Z/SnPHks27s1lw68zak/vAHhuLkpf+E7DUL8uSkcLbwsqOM8VeyfvB88LDygES88MI3KvHp2rjwx6Su80YWYPFYQzDzp9CM89FikvEJklrx3P+C81mB7u6xvr7woSK86VVrDPJapUDxTcPM8kAODusZ7iztDsQW96BXzvCO0dLyJ6hK8k+a4ulrfhDwXjgs9VGYMvVewDbmW6hs6SRYuvPwa+rwQcRc8bUKxvLGBmDuRbPm66XQGvCOT7DsLbZc5H3iAOwt5TLzNnIE9WLC7PGhlpDxype+8D8GrvGu2F7zkdEO7iqdcu/rTKDwljuY8m+WoPMpF3rr/p+A68SW0O5vY3Lq9y6u87JuuOjauFjwt/K+7DjjDvCJ+nrxdYya8IGNqvOwnmryYAry7dDKxO3l+GTzcM4g7kf1tPIDXzDr5VwG9jDIxvFaIXDx9zE88AJuGPCR+VTwB/sg7rqiRuzoqBT2DKx+9jI9Hu6lVO7ys9EK7dwk9PFQSR7xLRzI9FqRJvG44xjxw2T29tIPVvNr3bblTpZY8xqyAuX6ZprzZlIm8iEwvuzvkVLynr6u8cChiPNhT9Dt11Xa7R12EPGz+xjtmHWG7fEYEPZaTHby5Rz483y4rPGNxHDyK+Bs9QZgvu90cq7y4XiW8SJ8dvUfdQ7ymMM081ZTCO/6QoryGGH89+VEgOx74TbyGXgu9ARkMPGgyA7ugE+27FroUPAtGDT1mzZE7Ml/+Oi3mAjwaIJY8FmVTvHazOTu9hQE8oiAJPOrJgDyUXIQ8U6PSOxJ8czyRNAq97MHgvGIyebzIdq28nDQPPcjXJT31wUS8QeJHvPwvj7zx5Y281RxNPBADo7s0/qW7YvF7PN5W5TzPZB07GfTCuz44oDvjNHY8rjSKvPVfCrt302S9eYm6vDVypjwha0U81K/Ru8lc6ryrpHU86q7aOct8ALzlsV+8mY4ePA0zuL2qeIS6t4qUvDBauLrU9iA8plBovEugZrztEqG8uTAkPWsnpTwuOTK9+M6rvI3ef7xcGiU9p8QEvTcBsrvrEJo7skqLPGlonjwE1dy69i4APN5tcTxtouW8o6pivMjWfLp05Uc83FpvPAT71Dw73uQ7dLuKu8QgDjxja32885g2vMLfUrzbsbw886nWOsOiWTzrcTC9LX7zOz9CqTsqHtC7zzgePQRp6zt3deC8BvFLvFP20TwKdzQ8u3MlvOi4iTvpmx49Lsmeu23LEzy6iR09YauyvI9hjTxlGRc7gmKDvLOC17wVsWI8Jaf+u2QWyryyvAW9BxL6PNgl67y/RNi8U67KPGWojTyrFgu9oOo4vMtGmzoAz2m8g64+PMbh5LtQNZa8HX+0vGmu/zvQ8R49n/uPPB+IyjxoL5+8DTH5PHX1XbvAKy48E4OxPC06PTzXAsC807IWPSQv0zyk09y8qjztPFhjgzuzdiW8CNoSPJD+Ab1ETQG89CRqPItAETzudC+8ju3TOsStLTt56MQ7M/CNPMMEi7wOmJa6frGPPO8d2Lxy2Qy9JrJkvL9R0LyAZqy856yFO0fLzDy+RH486qyFPCFdIr25Rds76TesPMcri7wUsxK9I45JO23Mqzv4lTw8vThBPEVQy7yt3H670PYpPF7SIj3Rfxs8RTqQPJsch7y/OUE8oh1punhK2rvO9yG96UflPFKKnrvFaqM6sy/WvA4hcTxtuD+82qeFPfriqDsya3y8KWppu8HkjLv8BIW51Gs1PJ5XmTxQJtO8u40OPYBoG70gddi8VSTIvL957jw4GvM8Ui7avOHqBLwqEvy8kAKnvNsRwrwqbgG9FDy5vNl/ertLoLO81TMKPKIIlL2zrHo8roHgPAwC6TqQubq8KSmOvGgU+rrMxyw9RQwYOSTDJDx6HyO8jJC+vOQzvzx5hXC7v67jvHKMwjwvKc+7+dsWOznUNjxNlcU8VZENvEEs2DajzZq8znJSvITEQTy1iuI7Qo2ruxfUYLzT3wA8UganPNmeDjwocQ49Scj5Oxf/Izwq1DG8t9JEPDIyhrx4fok8PGahPJCI37uK39A8lJ+7uxN6zTpbnkM9dqamO+KiCjwq5p88m1nVvPJ7FLwT7sI7I7C/vAHExLu2a+i8cjWnu7ZnoTtcQwi9YXmLvPkWtzwiyAC9HBajvKwmrLwsrpO8fQtWPMOuYbu+E5Y8Np0sPClJe7t1AhC9ftXBPC7fWrz/sw+8nIhyPPaQ5zxokXK8kskMvc8LgbwFQwM91ISKPDFSEjq9Nd065IwEPeIL8jxhU7k8CKy+PDNAtTxj1H47A4CKvHviETqzfLS7RVNJvOHeorw7Fvy8z9pgvP/AkzzsGna8aJy/vCxvlzyjChK9P3Cfu+P7sbxPZQ28TvcjPAzbZrtB32C88iwEPaaixzzvPaw7ztqnvAVnLTwk9iu9rruHPAHexbrDPHw8p1QyPLG5ybvZJRC8CQm7vMKGVzy4S7E7y9fyvOTT7zykJAC96Hr/vA8aNjwcvBQ9+RkPPcPBHTxWDrK8PPEwvIMfBby1ZKw8hnPmO1DrIDvK0uQ8vDphPJqKnbzEqBQ4NOlMO417j7uSB5e7y7wgum2BWDxc9gy8YnbivMrzLjxZfcG8KsbpPAze07w469y8KStUPOTmCDwlJ1+8VwhrvBYZdTxKF4u7NOZjPJUKwLzQQUC7lg2Ju43s37wI0Eq6FzSIvFehLrzVsAo7w049vUnyHTxXYCo975ZCvE6ZzLxgZq48umSLvB8v5btB4KG8vn/avH+mmzoxhFG6autJvACiirzFV1g9mpvfO61FgTxJE7271QhPPOLRPTxtWIM8H+rGu/0NjLxCKTy8zXi0PK3ARbwY1IG8K/sSPcyZkbyg+CW8cC8MPd5RejsHvi09srm2vI2rBbxfSvM8v6l2PDCYLDtqX+y71xpSPAtvJjxkOvE8gVafOXhxUDwl4v47TsVnvJZEdbzl25y7HWX4vJ5cM72DRT2868PjPKfrsLs79ha52rUqvCTz1rv+LGq87DYRvAE6Ozz6uVW8uhe4O27kMrz+Fzs9IqkvvDZuYLxN8Jm8XQkzPJNruzzb4qc7+JfgO/yxg7ybhpo8+5i6vM84HLwzLf+869o5POsKNjwfaS28Zg7ouxmoUbsAVDO8mzEZOwO5oDtUGME8xHqvulC3RzwtmHS8+0baPEPl67tbkju8DfTJvLnfhTyTC5m63us4PQ5GiTzOZQC9YZzsPENbOLtR0k28YSfgu4DlWTwfuU27ecRTO0IFHb25iO286T89vJiiITtukL48xwatvOGbjTx0JQS8caHpvAZoTDzKSyQ8lGdiPBChh7zDyIQ8ns9PvIITx7uGURU8nxCbvN6wGjywKNO8yTZ6u52Q0btlbwG9A7xFPHW4HruFmIs6za+SvFxesrwyHDU8c1ptvK26Mr2NdjC8sUmkPSmaEr1HGk68vmRcPN07Dr1KZ/Q6pR4LvVe9PrwGXeU7odknvMqpRbzgaR68NyUivFOkCbt8GAI9/8bTOmwAXLx50VI85J5BuzUDHD3WMR29nsPSOwF4CTxjqUi7eeP3uGTJizymhAY81s+nPGRFzrXVJuA8318DPLlgB7w0Yiq880RfPMBbUjyEzqC7f7ZevBKZsTyShEU8wyPouml8jzzSjX87rcGMO0YOFjzi4vM7jlZovA/BlbvTph+9qf/vOqvcqDxTvlQ6zEMIPZSFr7unIAw9elKZPJnhLz1SxB+9wO/0u4PdqjuJE1a8SiS4vCC3D70Mvp+8u/m/un6/VLpzsZE8vQ0cOm2wQzt6qnc7S0HCu1cXqzzdz9E8vocEvYVKHj1QU/g8udb8O3Jl97xKiZ08JIvIO6vQrLnLNlw8HGMjvfxKuzwWap28mW7YPPenHLvY2oi8VkylusNA7rznhz+8HvgYPO6a3rm/Kw46Lw2euigPSb2HfuI8oz2svKaDz7snLnk8q9MhvL3MYzoTgjW8j7hsPPXNE7yvo+q89gYJPOpEYrw7e3k7ydL3O6seMzsmCCI7SPPsO5K0rLwmQY88pnc8PBMWfzzGUKe838+FuzKybrxG7qi837PqvAULjDvczXK8LKumu3LKm7xiqDG8FqsuPGaRXzoLS2C7VolEPKjRpTzhOkA8i1fcu3VlLbt69pC7TlFTPFlthjzLwTi887LJu8G7UzzZogs6+ailu22QCrvqBi+9vt8xPPSqJzs1KMi8SKMWO3uoNLwGxuQ8C7VIO2z5GLzEMOU8jLzFvKeQ3Dq96y685z03vcS73LwzONG894OzvEmtJrwH8Ea8cgEhPauki7vTUka7B7AzPPbhgzt6vxC84t+LvBQ+67vLRo48CLVJvDCkAbtE4pc8HkgWPQqsvLz0m78888sgPFUtRLzJcQ+9gu1WvDhE3DuqC627e7MRO5IIBjtrDeY8n1VtvPcxkTxGFfc7pfaRPAsFErxy4l47/SN0vBHyATs2BbC8uhEqvH7/jLyeDqw859Qsu72eBz01Ocu8J/sOvUNVEDyZtaW8GgwIvYCIubxayJS6CzCXPDMbdzz1ah+68gJwPbASkTu99om8NFj3O0jo6TzbwyM8ap22uvZE6juQijW7nEU3vNA4DzoO8wE89XufOupkPzxEUCA8SPgKPCdLD70PkqK8BsLnui/UDbsI36u8sp5zOD5z0Lv6+0g8wcOHO5udIz0woyq89vgxu8VbijzgTrU8iX0nvQNK3jzSd2259EMaPOfQjDzZmcu8pPWBPINfA7vKzlw8CiuQPBtQBL3tQ0k9B4juPDUPqbt7/uo6EgIVvcH7nzyFt6U7QAZ1vCZZHbvD0Zq85TvBPNQ0VjqTEhi8SzQhvGkA5zvdmCU72Uy6vPjzU7uXeLo6IotkPCXtubwXe808mXCXvGe2VrwEa6m8ZQkvu+gqG71DAVA8Bf+AvCXdPTxoA867wDUtPB3bpTzI7YM8xQ7nPICy7rz2cpm7DszUO1gWxjyyR0e8AnS2utAGqjyYW8E8MFGNPP1ozrvOoO87oBdvuwMNkzqcVYu8rwUDu1LM3TzHawm8r7sZPbSsvjzTU6C8WTNRPJ80fLsPeI88tZXEPNj0QL2uHlu82UqlvI+gJr2DYwk9t7sjPaxiL7wL0C885TWpvOdQHj3hiUw9p91Ju5GtQ7w7z2I8OTukPFD6EjwI/Ci8H5eHO0TIHz3Zn+o7ueaaPN2+oLzxM6S8l/d6u+0PpzuqoZ48wQOovNLqmrusFZ68V+AAvDIopbylQPW7kVCEPE8ydLtO+u469FcFPEXFCTyzlSA9+G4nvAma47va4Xe85FowvC8ai7x6URy9Tp/Ju5le1byHqMQ8YLQFPcFVn7sCAze7dHMZvSChhLxRU8a8u6kcPb9ZfTvRgj69CmhUPEvZnrzHZYu8YQYZvcicCLymg0e918enPEFPMbsg91G8UmovvVTnLrz9X+S8Bv6eOz0uAbxMqw88cVUTvLpS+zxoz7e7pvndvIi02LzOd228btUwPI4pc7zc5YC8RfIgPaknzLvs+Ee8uz89vLb3H7xKGA28JwomvFnTwLyoyEY8LACEOwM5VTxQxOW81Mrku2Zbmbz/tBK6P3tlu6M2KbxVU888e5/VOxjIwTyJ91q87trxu63LrTzkMCy8XELzPN9Lm7wYu5K89FIjvCw4MryT55k80y2nPEmLZzwEX668xRakPI+JgjxGnQG9l0FBPIy0ZjwQlB88jFjDuRMoGj1WzeW8gwvQPMmrEjxVlsk8Hwr1O5znoDzh6L68ElV9PGDrCLw5ZNC8SM6YO7FqCjymyga5+OuaPEy77zyvzru7U3cgPW8ClrzL27A6KqmNOhC0ALxayoK82+RFO2gFe7x9s5O8DlMXPB0/sby4v228NFiJPPPOqzztF/o6oXMRvVkpmTy6ma08OnqlPN8xo7tT8ae8RisfvUZmpjw5+iW7UpP6O67f7Twad0e8sMg0tXySjzkQjL47PwVZPHM0J7xb1Jg8hlB1O25fnrrF1WU8XyGsOYoWEr0wPke9BYD2ukeDUj0ZoeI6biMbvXJ5Eby09le8o7pBvCXIWbx43Sm8uRUTvOM7Cz36DSo7UwiMvGdZKTuo0p06t7YmvFVTEjxYeQA9vqEDvITqFbwRIc08mPPSu8MIHjzXCxE7AcOsOqGsiTzaUJs8L+qSPM3IOT1M4Ys7VkIEvT/c2rwLsQE9PpJgusL5wLxT7uW8ADR8u2vJdTxQa1K5FAiLPNR6Eb1CFvU7bioZPf7vqTv6tqQ8k3/hO3VcBT1sg/g7ECfrOz+wVLxWR2A8htHQOwx34zy/LO27eghHPeOBPjvVDI283LFtO6INJrxj1Mq826tmuzpmsjyRuFu6oqQrvLsQ0rtO+0U9g3LhPIpRIjwimHK8JLatvPQHsrqyGhM9FcbxuCDgYjwz+F29gpLxukTGxbvV7r+8GP87PO9uxDy+3Qs95ueou7fKL7zyvbA7XX9kPH/EhbyY4ZK802mZutGYW7pqKxW9v69Vu7pqK7w52B+92s62PMVqubwVd+68xj77PB1ht7w3Iio7YO4IPBa3s7uBsKc6bjQWPCpD+jxYMGq8Cd3pPBTj4LsizqQ8Yhr0vHbGhrsNPO47jqg0vX0OWDy4LTe7QP6JPCGA9LsJfYY8jDmUO1YJP7vBXSg8vgJwPMi+grtMWfa5K7lFvNymrbz2+3Q7Kfr5O3XOaTzL3Qs8bVT4u4/rIrzMADY82zaLPOsCeDxFE1g8VUMrPHV75Tq+HSY8oSeXunyWY7zL80K8oCDzPH8FMzsJ7cW8PoMqvT9GDD0+uJO7srpVPJ/6L7tlRWg8Zf8GvIu/DT3oNY+8NcoqPMx7n7zMtVi81RTfugBLNz03Hia7Li+vu1gnx7y7OXY7xFOpPKcuQz3wEFe84PmhvDDj2TzD+ds8JYUbvPhPyTsKZVU88ksuuxXvVDs5nMm5KeZ2ObCn+DuqBIc6tshQPNzRebsY7Ai9mwMWPKuAJTzoVZU8QhoXvYIJwLttKDO8PV3Bu5kPizy0Ae66ZsLEO6KxfzrN34q8Gqz2vDYIED35zQG9dU2FvHZXvjzZeo68Hr2dvADtjzqjctQ72fk+O8vNBT0Ecxa89z0XPAKX6bwk7Sw8v9/EOwzSqjw217m8o48hPXmUL7vo5wS8SlPdvCvjwjwvS2c91ZmDO/97fzs69zW9yCIKvTS/NTwjbVc8uCi/PPBxWbzDKaq8qNyguzHiCL1pZgg9EpVovOx7OrxiB627C6CPvJqVBDxLtCm9mAo7vIzNK7yeHFC819y3uw4OiDwvz5A8AoYNPd2GPrx5Ceu7vncMPJ99YDwmUpo8sf08vAKH1Ds9zZm8Ub7Huklm9LzL7de8oqEzPaStqzzAI068Wl3Au8ULhbyYDWs6hMQAvZYUHD0lnkW8QjXevCX6BT03XTc8x+8UPBmEPr3uKOK8mxuvPPZ8db2r9da8xqEovFh+ODr32cq8aSQLPR2rjjwhnFe9qmCBPB1khTzoKim8AChau5/dOrwUsS28IMf6vAOzGz1LBZm7dwEyPLIM8bz5QvK89laUvN+aBzyWf7C8r5BKO02kArx8oBk8YqHLPNglzjyfChO8HIjOO1Pn1LxS0wW7a4k9vGlJAzypY0w8fCnvvAfyYbqcLEU89OQlvdq47rxgGrM87Jx9PAgDo7suNqU8yvnSvLyzybtnfee6P5KOPNkwMT0RUAs9qhi5O5rmsjzpktU8WddFu9SfAjyLjhu9vqqXPGMLi7y6TOg6TM6Du8wg8Luyr3C8cX7avODSMj029968EhGbvCcDojwklGu7uAN5PBWpqjx+wTg8uriQvHe9vzty2i49oVGXPNJRwLz5Jpu8KJATPVImpzxsPAG8ZRWEPFwQizsj8eu7caQguxInvbzDGny9WF3lvPBt9Tttxg+8HUc/PAS2NbyNxZI7Y+f9uyyZ7TyGQkQ84wwLvAwB+DpMkVG8kinMvGRyujws/Um8E7CuvLAraDcNRpM8DqzKvJxXjLt8P1q7Vm0YPXKWzDzsnOo78boPPNp7p7xedxm946VyvGSpoLr3S0S7+fVzOqYt7zyvLwO9JFiJPQOfSrx6aeU8H7etPGC99bwO2cA8PkcGvDa5xjz6qVm8/qPFu23uJDxdxDq8oQg0vCKlJz3P5/88QKGUPIv9mLqp5/I6aNDBvBuoHTyK3V082OJNvMU5H7wZWAC96LFmvLWycLwobtu8J/Q8vR9JtLxD2/S8QEO7vJDktLnWmwM9g+t4vGAXpbtJyj+9eaSfu9VirbsYIWU7/08BO7BRTLy2u2Y8URR8vILipjzsc0C9UvoLPEVnP7yUl+G8g+hfPOj1BL2wk967amAMvRljNrz3D9G7C6kMvEqshrweDHC8kekDu+OP1bwAAnq8ks8sPIGZyDykQMC7asPWvNTPzLzTMqA7OFVePC2hwrugLKe8jSepPLCHezzRtSW9o+YSvCP+0btm1H27WqeUO+WfbjzZwTu7WkqjPBbPYjxAIZ48VCwlPeexBrzC8ju8rsfzPI8j4Ty4lIk8nyEHPZjNVDthyBI8J9/9PEIIWbvfmka7mEEiOfJLKD3rw1Y8tES3u+VFtTzDKNc8vm80vM1werwkHYg72+jgvNU9fLo3qxK8/z0CvbDEKrthG7s6bzkAvAe8wjxsMJO8Wdrhu3LFHz3u29m7RiE3PNQWgjxdn9G869zoOogq3TwzYA08REEDvMTR0zyT44m8s4eNu2YsMz2JgQG7v5ueuhJjQrvFz+k8POg1OyrQjbwPW/67dUdKvCjRITztkg+9w0ZIOlGLAjvBtag82tR+PO8xHD2V3Pg8pUpKPFcyqzsvXZA82NUFPSJL67sRsWM7AQMKPQTB7rwnCSw8VdGzu/LLvjzNFaO7ufuOvL0H7LyFQpk8UPwqvHBaLr1uf3U8rY+PvCsMFzzyY+47IdGuO9UBTbwTE2s6IPYZvY9s97xzZli8LTh4PF3SDjxnF7K81iJIu9F3brzrPxu8ug7pvI8mBbxmvHA7P4MbPdWgTDwE3SG9wSOhPIs/XDueL4i9VRPTPL87RrxbQkG8Z12dPCY9PTyteCu8Z0tsPRpgsrywfS08tZX1PHE+FzpcHIm8pDpMu/kAHrok3aO8t9ruvJ+hVzwIK+Q68iQBvI8MCLyVbwO9kj1CPaBKCz0hVc27JuW9vPg5ADttSQW9Al0IPeW7sjziIrg7SQJnO/ts9byQhfI7nFGqvOo/h73zQLa8WPySO7W26Tw+L5e8VSCgvNzmpjttoBs8G5OevGA1WjvvnIU8PrclvflxGTwmZyC8lvIyPV/eZjucqQM9Zkm0uz5a5Lz3+cw8KgqRPCpT57xyUeG8DVqSu3HcObwxzMa8PoqhuxoO8LnCDVm7mNB+u72B5DhnrvO8FTlAvD1skjumzOo6jTHTu0dCHDyr+CE9fxYaPEJXSjzEpBs857OQvGKdBz0GNxU6d7kPPL5a3zzDFPS8yH3jO+xnmDyhpia9geKkOuVVajuX7fm8EPwYvHAzBD1B+3W76bQLvWjzgby6SOS8ulK9u5nJfLyZfP+8DCFjO9pEIj3DBe27gR8UvVaS5LwhH2G8wxjUug3EmTwAtQ+9QNB8vDvyzjn3q5q6pYhou7fmQr17ej67r7TEPMMOvDuQkt07+xvpPMt6nrxMjGA8BvMkO9lvwbzLApK8IY0FPK/rELxcyUa7Hq6Vu0jIBT3+BI082uadvMKJ4DwrqmS88qBzvOesojwy3Y+8Uo2JvEGiATwGZ8K79G0YPemen7zD1wo8wllaPN40p7yKElU80fJqvJpyubznjxU9lmCFO+KburzWPp0805XUO/VsJrvswhs8Ogl1PFsXA7y4/IA80RUrvSw1cDwapWu8m04BvI4/sDvfN268eGrCPMwZBznIYAq9tS6zu6oHND0D3oU89Fbqu8jlLz0Axq28m53xPKzWfDyHbxA9Q2KuPNmW2bv+t4A8ePQdOy5yj7ycLy48RKLWvHyItDuf6Cc8aMIEvNVkLLwxTBO8+uQdvJDdDjr311Q8pNknu9H4wbzCese6VJgDvaUKSzqag/28IF+KPDXJzTur4JI7KyTlvBUvlrxOk+K7bpPSvC+hrjvuqXm819NdvCqyG72D45c8VO7fu3FRTLs+S3Q7YTKqurwpJjyrz4Y8Ckvcu0W+mbtMpNo63oQRvMjdCTy49tU70+BiPPSWrTrPqkO9349GOhYlpLwTkFI8kcIrvdpVM7onylu8QrCrvP/1DTsyEAK9pvcTu5boKbyLFqo7QJG+vHzZqrzZBh09Jm1JvZkUpjyjRbI8ew+pOyZPdrtfgO27pXLHOyb/5DuxGBe8IKcQOxKmMLuAF9c8lFxMPDoGDTyjIJm70voouj0E/rtoU7S8CltEuu3elbuM7tA7aXTVPNSs8LzIeie88lckPJknvTpFkyC8sfJCvFLNILxrova8ea+3vO4qDryBjII78RcnunkaTbxHKSk9JwecvLck9Lu0S/e83WrTuiGLlLvpzJA7tuQ1vBkGa7zrGSI8aeRPu279Uryi/pE7Y3SHu+3emTx73bC85BYvvEO7FLx3CrG8ofypvINlDb1aAdu8Fl5PupbcKL2+wQu80C2RPAY+Hj0oRw+98VlHuk/eEzzU0z67OJihPAaCDTuRPbY8pdqJPBy9DTx/0OK7eF60vGZl6zxPdX48CN8MPS8mdryDQbI7WlRDvO/d7zycYAy8RFk6u7sPYjsPrXE8bsC+O6IRrTyyuom59gXMO3ZpuzpxUBU8jlOkO4APiTt+U/G6+NwFvTgAgLy0Dk4729/UO5vPHTs3Sc47j2b3u1AdBb16cGe8OBqMvCwnOjyGIQA9L62AO7eTqrzGNis8vU+4O/GusTwNYoc841Kaux0jzzyGZie7rWy4vDLuQDy3qY28UqAdvKtl6DpO4li8Unk+vRAls7wxcek8SA6MvGwWKzwhs2k86ruVPBpYNDsJAfe8ThbWO7rerrvLIjG7YcmrO61i/ztfG9e8Xb6XPGx4IzyVV5s7iH+GPBJbXTzQqpI7jb2KvBvlSbs2Ytm8nRQ8vPPIsLntsiy9cghkvHC1VLzGFcu6573JOtwqAzwx2Ry8lRfVO6tV1Lz5sBS9+Tp9vDwzpjyE8Mi8K8kOPcIO9bute4M8ccgJO3qgJTz7/Lg8YOX4OsHjUzyUIEg9BCjau3G+0zyW3yS7GJYNPOmnlLvmAeE7SXAwu98LsbufOQo7biL7O+9ncTzWM4k7tYtoPDoQIj13UMM8VGAJvEy5Qby91ik6z/IZPU5ah7wXM7K77X6Qu/xYcLyt69276YUjva2xQzwWYHk81jjKOVZ/njuc3Yw8KZgqPG/zBzydc0K7Aq6ZO8CeQzu2tq+6IZOkPKzchbz9VDK8FXnju0aTP7yB4sE8UkuQvBb2R72RQfO7utGrvDCrrTsGF9u7Pe7tvPb9Ib1H9p+6NlO1PMn1VzykW827ArLpPDVWtrxX8RK9/ByAPDteYby0i7g7c7CZPPMykLyoRY48sRlSPOLrFzzGKqM8gdqrOzbuDDrzraK7CQjMPOR9jbsghbq7vhZnPHbZpzkI4bu6UUqAuznoETwj7l67zPy9vBrJJjydX3c8UXYDPWWthDy/WYc8xAlbu9l1WLzC0co8mrJ8vCc6O70egB+7+6IgPOLSBTw6kfi8ujaLvDqbMzoyVgG9AWhOPEHRRjz0xj27gScfPBU/iLxuIsE8dJ/AO0fikzw7s+C7R1zju+lVNDydMzE81B5/POl6bbsdRQU9sSYcPCb+Gjyqi4G7HeS2PP1DcTo1PrW8rCOBPK/kDDxgCH28aVpLOhemzjy0BoK8DZc/vBwm4LvPzOi8ycG9vDKL57sPmcW7cFjOPNUSoDvAVxg8vLmOvMs6hbynpVC8ztP4uwgshDwPrIW82qjmOwGqhzx5W8S8yAE0vDAkQTyjp2E8aQmNPCKY0Lx2PD87GXgtulEdRzud3EY8rVgdPMGhdLn+aCQ7eX9su3dBLLyrY7I87qPlu3qrrjxnpAA8S4w6vLtZPLyIeBe94zSsPA/uZjwRpuw7wow+u5rbCLyo+Ms764BRuyLGBLjoDZk8SD6FO14pr7zv//u8SFefO/cF6TsvgaK8tJ90PA== + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 6 + total_tokens: 6 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_variables_persist_across_executes_after_vfs_read.yaml b/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_variables_persist_across_executes_after_vfs_read.yaml new file mode 100644 index 00000000..e0676a57 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_variables_persist_across_executes_after_vfs_read.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '85' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Foxes and dogs. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 6 + total_tokens: 6 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_vfs_loop_and_connection_are_reused.yaml b/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_vfs_loop_and_connection_are_reused.yaml new file mode 100644 index 00000000..e0676a57 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_vfs_loop_and_connection_are_reused.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '85' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Foxes and dogs. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 6 + total_tokens: 6 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_vfs_reads_while_connection_held.yaml b/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_vfs_reads_while_connection_held.yaml new file mode 100644 index 00000000..21665a96 --- /dev/null +++ b/tests/cassettes/test_sandbox/TestSandboxHeldConnection.test_vfs_reads_while_connection_held.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '106' + content-type: + - application/json + host: + - localhost:11434 + method: POST + parsed_body: + encoding_format: base64 + input: + - Foxes and dogs roam the quiet hills. + model: qwen3-embedding:4b + uri: http://localhost:11434/v1/embeddings + response: + headers: + content-type: + - application/json + transfer-encoding: + - chunked + parsed_body: + data: + - embedding: 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 + index: 0 + object: embedding + model: qwen3-embedding:4b + object: list + usage: + prompt_tokens: 10 + total_tokens: 10 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/sandbox/test_sandbox.py b/tests/sandbox/test_sandbox.py index 65272d41..78c5b258 100644 --- a/tests/sandbox/test_sandbox.py +++ b/tests/sandbox/test_sandbox.py @@ -412,3 +412,136 @@ class TestSandboxVFS: assert "1" in result.stdout assert "Public Doc" in result.stdout assert "Private Doc" not in result.stdout + + +class TestSandboxHeldConnection: + """VFS reads must work while another connection to the same DB stays open. + + Mirrors the analysis-skill lifespan, which keeps a read-only connection open + for the whole turn while sandboxed code reads the document VFS. + """ + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_vfs_reads_while_connection_held(self, temp_db_path): + """All three VFS readers work while another connection to the DB is open.""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document( + content="Foxes and dogs roam the quiet hills.", + uri="test://doc", + title="Doc", + ) + assert doc.id + + async with HaikuRAG(temp_db_path, config=config, read_only=True): + context = AnalysisContext() + sb = Sandbox(db_path=temp_db_path, config=config, context=context) + try: + result = await sb.execute( + "from pathlib import Path\n" + f"d = Path('/documents/{doc.id}')\n" + "print('foxes' in (d / 'content.txt').read_text().lower())\n" + "print(len((d / 'items.jsonl').read_text().strip().split('\\n')))\n" + "print('tree' in (d / 'toc.json').read_text())" + ) + assert result.success, result.stderr + lines = result.stdout.strip().split("\n") + assert lines[0] == "True" + assert int(lines[1]) > 0 + assert lines[2] == "True" + finally: + sb.close() + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_vfs_loop_and_connection_are_reused(self, temp_db_path): + """The background loop + read-only connection are created once and reused.""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document( + content="Foxes and dogs.", + uri="test://doc", + title="Doc", + ) + assert doc.id + + context = AnalysisContext() + sb = Sandbox(db_path=temp_db_path, config=config, context=context) + try: + assert sb._loop is None and sb._vfs_rag is None + read = ( + "from pathlib import Path\n" + f"print(Path('/documents/{doc.id}/content.txt').read_text())" + ) + assert (await sb.execute(read)).success + loop, rag = sb._loop, sb._vfs_rag + assert loop is not None and rag is not None + assert (await sb.execute(read)).success + assert sb._loop is loop + assert sb._vfs_rag is rag + finally: + sb.close() + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_variables_persist_across_executes_after_vfs_read(self, temp_db_path): + """REPL state persists across execute() calls, including after a VFS read.""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document( + content="Foxes and dogs.", + uri="test://doc", + title="Doc", + ) + assert doc.id + + context = AnalysisContext() + sb = Sandbox(db_path=temp_db_path, config=config, context=context) + try: + first = await sb.execute( + "from pathlib import Path\n" + f"x = len(Path('/documents/{doc.id}/content.txt').read_text())" + ) + assert first.success, first.stderr + second = await sb.execute("print(x)") + assert second.success, second.stderr + assert int(second.stdout.strip()) > 0 + finally: + sb.close() + + @pytest.mark.asyncio + async def test_close_is_safe_without_vfs_read(self, temp_db_path): + """close() is a no-op (and safe to call twice) when no VFS read happened.""" + async with HaikuRAG(temp_db_path, create=True): + config = AppConfig() + sb = Sandbox(db_path=temp_db_path, config=config, context=AnalysisContext()) + assert sb._loop is None + sb.close() + sb.close() + + @pytest.mark.asyncio + @pytest.mark.vcr() + async def test_close_stops_loop_thread(self, temp_db_path): + """close() tears down the background loop thread and is idempotent.""" + config = AppConfig() + async with HaikuRAG(temp_db_path, create=True) as client: + doc = await client.create_document( + content="Foxes and dogs.", + uri="test://doc", + title="Doc", + ) + assert doc.id + + context = AnalysisContext() + sb = Sandbox(db_path=temp_db_path, config=config, context=context) + result = await sb.execute( + "from pathlib import Path\n" + f"print(Path('/documents/{doc.id}/content.txt').read_text())" + ) + assert result.success + thread = sb._loop_thread + assert thread is not None and thread.is_alive() + sb.close() + assert not thread.is_alive() + sb.close()