personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(install): narrate make install-service phases

The install path produces ~4 short stdout lines, then sits silent for
tens of seconds while the old supervisor drains and the new one warms
up. Narrate each transition so the user can read top-to-bottom and
understand what the seconds were spent on.

- service install (Linux): label the systemd daemon-reload and enable
steps explicitly.
- service restart: print "Stopping old supervisor (...)" and replace
the misleading "Service restarted" with "New supervisor process
started (warming up)" — systemd Type=simple returns at fork, not
ready.
- supervisor main(): two-space-indented milestone prints (flush=True)
for maintenance tasks (when ran>0), Callosum bus, convey, sense,
cortex, link, and final supervisor-ready, gated to match the
existing --no-* flags.
- Makefile readiness loop: rename to "Waiting for supervisor to
report healthy...", trail dots on each failed health probe, print
"Service is healthy." on success.

UX/transparency only — no timing, retry, readiness-probe, or service
behavior changes. Bundled formatter drift in two unrelated tests
that make ci required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+41 -7
+7 -2
Makefile
··· 498 498 CI=true npx --yes skills add ./skills/solstone -g -a claude-code -y; \ 499 499 $(VENV_BIN)/sol service install --port $(or $(PORT),5015); \ 500 500 $(VENV_BIN)/sol service restart; \ 501 - echo "Waiting for service readiness..."; \ 501 + echo "Waiting for supervisor to report healthy..."; \ 502 502 READY=false; \ 503 503 for i in $$(seq 1 20); do \ 504 504 if $(VENV_BIN)/sol health > /dev/null 2>&1; then \ 505 505 READY=true; \ 506 506 break; \ 507 507 fi; \ 508 + printf .; \ 508 509 sleep 1; \ 509 510 done; \ 510 - if [ "$$READY" = "false" ]; then \ 511 + if [ "$$READY" = "true" ]; then \ 512 + printf '\n'; \ 513 + echo "Service is healthy."; \ 514 + else \ 515 + printf '\n' >&2; \ 511 516 echo "Service readiness timeout after 20s" >&2; \ 512 517 exit 1; \ 513 518 fi; \
+1 -2
tests/test_journal_index.py
··· 1765 1765 snap_after = snapshot_entities(journal_path) 1766 1766 1767 1767 assert snap_before == snap_between == snap_after, ( 1768 - "scan_journal() mutated journal/entities/ — see " 1769 - "docs/coding-standards.md § L6" 1768 + "scan_journal() mutated journal/entities/ — see docs/coding-standards.md § L6" 1770 1769 )
+1 -2
tests/test_schedule_schema.py
··· 48 48 schedule_only = { 49 49 a["id"] 50 50 for a in DEFAULT_ACTIVITIES 51 - if "Scheduled events emitted by talent/schedule.md" 52 - in a.get("instructions", "") 51 + if "Scheduled events emitted by talent/schedule.md" in a.get("instructions", "") 53 52 } 54 53 return {"meeting"} | schedule_only 55 54
+17
tests/test_service.py
··· 7 7 8 8 import json 9 9 import plistlib 10 + import subprocess 10 11 import sys 11 12 from pathlib import Path 12 13 from unittest.mock import MagicMock, patch ··· 317 318 result = service._restart() 318 319 assert result == 1 319 320 assert "not installed" in capsys.readouterr().err 321 + 322 + def test_linux_happy_path_narrates(self, capsys, monkeypatch): 323 + """_restart prints stopping-old + new-process-started narration on the Linux happy path.""" 324 + monkeypatch.setattr(service, "_platform", lambda: "linux") 325 + monkeypatch.setattr(service, "service_is_installed", lambda: True) 326 + monkeypatch.setattr( 327 + "subprocess.run", 328 + lambda *a, **kw: subprocess.CompletedProcess( 329 + args=a, returncode=0, stdout="", stderr="" 330 + ), 331 + ) 332 + result = service._restart() 333 + assert result == 0 334 + out = capsys.readouterr().out 335 + assert "Stopping old supervisor" in out 336 + assert "New supervisor process started" in out 320 337 321 338 322 339 class TestInstall:
+7 -1
think/service.py
··· 287 287 path.write_text(unit_content) 288 288 print(f"Wrote {path}") 289 289 290 + print("Reloading systemd user units...") 290 291 subprocess.run(["systemctl", "--user", "daemon-reload"], check=True) 292 + print("Enabling solstone.service...") 291 293 subprocess.run(["systemctl", "--user", "enable", SYSTEMD_UNIT], check=True) 292 294 print("Service enabled") 293 295 ··· 411 413 ) 412 414 return 1 413 415 416 + print( 417 + "Stopping old supervisor (waits for in-flight work to finish — may take a moment)..." 418 + ) 419 + 414 420 if platform == "darwin": 415 421 uid = os.getuid() 416 422 subprocess.run( ··· 435 441 print(f"Error restarting service: {result.stderr.strip()}", file=sys.stderr) 436 442 return 1 437 443 438 - print("Service restarted") 444 + print("New supervisor process started (warming up)") 439 445 return 0 440 446 441 447
+8
think/supervisor.py
··· 1672 1672 try: 1673 1673 ran, succeeded = run_pending_tasks(journal_path, emit_fn=None) 1674 1674 if ran > 0: 1675 + print(f" Ran {ran} maintenance task(s)", flush=True) 1675 1676 if ran == succeeded: 1676 1677 logging.info("Completed %d/%d maintenance task(s)", succeeded, ran) 1677 1678 else: ··· 1694 1695 1695 1696 # Start Callosum in-process first - it's the message bus that other services depend on 1696 1697 try: 1698 + print(" Starting Callosum bus...", flush=True) 1697 1699 start_callosum_in_process() 1698 1700 except RuntimeError as e: 1699 1701 logging.error(f"Failed to start Callosum server: {e}") ··· 1734 1736 # Local mode: convey first, then sense for file processing 1735 1737 os.environ["SOL_SUPERVISOR_SPAWNED"] = "1" 1736 1738 if not args.no_convey: 1739 + print(f" Starting convey on port {args.port}...", flush=True) 1737 1740 proc, convey_port = start_convey_server( 1738 1741 verbose=args.verbose, debug=args.debug, port=args.port 1739 1742 ) 1740 1743 procs.append(proc) 1741 1744 wait_for_convey_ready(proc) 1745 + print(" Convey ready", flush=True) 1742 1746 # Sense handles file processing 1747 + print(" Starting sense...", flush=True) 1743 1748 procs.append(start_sense()) 1744 1749 # Cortex for agent execution 1745 1750 if not args.no_cortex: 1751 + print(" Starting cortex...", flush=True) 1746 1752 procs.append(start_cortex_server()) 1747 1753 # Link tunnel service (opt-out via --no-link) 1748 1754 if not args.no_link: 1755 + print(" Starting link...", flush=True) 1749 1756 procs.append(start_link_server()) 1750 1757 1751 1758 # Make procs accessible to restart handler ··· 1806 1813 ) 1807 1814 1808 1815 try: 1816 + print(" Supervisor ready", flush=True) 1809 1817 asyncio.run( 1810 1818 supervise( 1811 1819 daily=daily_enabled,