brief_file.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. """Best-effort atomic writer for the durable brief markdown snapshot.
  2. Enabled via `[brief].write = true` (off by default). When enabled, each brief
  3. update replaces `<session_dir>/brief.md` with the exact text supplied by the
  4. assistant, using the same `tempfile.mkstemp` + `os.replace` atomic pattern as
  5. the rest of the codebase. `set_directory()` points the writer at the current
  6. conversation's session folder; until then it defaults to `brief/brief.md`.
  7. The target directory is cwd-relative user state and gitignored. Best-effort
  8. by design: any `OSError` is logged and swallowed so the brief writer can never
  9. disturb the assistant flow; `clear()` truncates to empty and likewise never
  10. raises.
  11. Thread-safe: the reader thread may call `set_directory()` while the inference
  12. lane calls `write()`/`clear()`, so both are snapshotted under a lock and the
  13. I/O then runs against the snapshotted locals only.
  14. """
  15. import logging
  16. import os
  17. import tempfile
  18. import threading
  19. logger = logging.getLogger(__name__)
  20. class BriefFile:
  21. """Best-effort, atomic, opt-in writer for the durable brief document."""
  22. def __init__(
  23. self, write_enabled: bool, directory: str = "brief", filename: str = "brief.md"
  24. ) -> None:
  25. self._write_enabled = bool(write_enabled)
  26. self._directory = str(directory)
  27. self._filename = str(filename)
  28. self._lock = threading.Lock()
  29. @property
  30. def write_enabled(self) -> bool:
  31. return self._write_enabled
  32. def set_directory(self, directory: str) -> None:
  33. """Point the writer at `directory` for subsequent writes (no I/O)."""
  34. with self._lock:
  35. self._directory = str(directory)
  36. def write(self, text: str) -> None:
  37. """Atomically replace the brief file with `text` (no-op when disabled)."""
  38. if not self._write_enabled:
  39. return
  40. with self._lock:
  41. directory = self._directory
  42. filename = self._filename
  43. path = os.path.join(directory, filename)
  44. try:
  45. os.makedirs(directory, exist_ok=True)
  46. fd, tmp = tempfile.mkstemp(dir=directory, suffix=".tmp")
  47. try:
  48. with os.fdopen(fd, "w", encoding="utf-8") as f:
  49. f.write(text)
  50. os.replace(tmp, path)
  51. except OSError:
  52. try:
  53. os.unlink(tmp)
  54. except OSError:
  55. pass
  56. raise
  57. except OSError as e: # noqa: BLE001 - brief write is best-effort, never raises
  58. logger.warning("brief file write failed: %s", e)
  59. def clear(self) -> None:
  60. """Truncate the brief file to empty (no-op when disabled; never raises)."""
  61. if not self._write_enabled:
  62. return
  63. with self._lock:
  64. directory = self._directory
  65. filename = self._filename
  66. path = os.path.join(directory, filename)
  67. try:
  68. with open(path, "w", encoding="utf-8"):
  69. pass
  70. except OSError as e: # noqa: BLE001 - brief clear is best-effort, never raises
  71. logger.warning("brief file clear failed: %s", e)