personal memory agent
0
fork

Configure Feed

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

refactor: route day-path construction through day_path()/day_dirs()

Chronicle migration prep. All non-test production sites that build a
journal day path by joining the journal root with a YYYYMMDD string now
go through think.utils.day_path() or day_dirs(). No behavior change —
paths produced are byte-identical. Once chronicle lands, the real
layout change becomes a two-function update in think/utils.py instead
of ~18 scattered edits.

Sites touched:
- observe: export.py, transfer.py (4 sites, one redundant mkdir dropped)
- think: conversation (NOT touched — see below), entities/activity,
entities/context, journal_stats (chronicle-breaker parent-derivation
fixed at 2 sites), supervisor._handle_segment_event_log, talent_cli,
tools/call (local-var shadow resolved), tools/sol (briefing scan
tightened to 8-digit day dirs via day_dirs())
- apps/speakers: discovery, status

Sites intentionally skipped (same wrinkle — module-local get_journal
or state.journal_root patches in tests are bypassed by day_path()):
- apps/sol/routes.py:49, 201, 455, 513 (state.journal_root)
- apps/import/routes.py:696 (state.journal_root)
- apps/import/ingest.py:207 (state.journal_root)
- think/conversation.py:105 (tests/test_conversation.py patches
think.conversation.get_journal directly)

These sites follow the same resolution pattern and can be absorbed
into the chronicle migration at the state.journal_root / module-local
get_journal layer in a follow-up.

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

+51 -66
+2 -2
apps/speakers/discovery.py
··· 15 15 import numpy as np 16 16 from sklearn.cluster import HDBSCAN 17 17 18 - from think.utils import day_dirs, get_journal, now_ms, segment_path 18 + from think.utils import day_dirs, day_path, get_journal, now_ms, segment_path 19 19 20 20 logger = logging.getLogger(__name__) 21 21 ··· 433 433 timestamp = now_ms() 434 434 435 435 for (day, stream, seg_key), sentence_ids in segments_map.items(): 436 - seg_dir_check = Path(get_journal()) / day / stream / seg_key 436 + seg_dir_check = day_path(day, create=False) / stream / seg_key 437 437 if not seg_dir_check.is_dir(): 438 438 continue 439 439 seg_dir = seg_dir_check
+4 -6
apps/speakers/status.py
··· 142 142 143 143 144 144 def _imports_section() -> dict[str, Any]: 145 - journal = Path(get_journal()) 146 145 meetings = 0 147 146 screens = 0 148 147 149 - for day in day_dirs().keys(): 150 - day_dir = journal / day 148 + for _day, day_abs in day_dirs().items(): 149 + day_dir = Path(day_abs) 151 150 if not day_dir.is_dir(): 152 151 continue 153 152 for stream_dir in sorted(day_dir.iterdir()): ··· 165 164 166 165 167 166 def _attribution_section() -> dict[str, Any]: 168 - journal = Path(get_journal()) 169 167 total_files = 0 170 168 total_labels = 0 171 169 by_confidence: dict[str, int] = {} 172 170 by_method: dict[str, int] = {} 173 171 174 - for day in day_dirs().keys(): 175 - day_dir = journal / day 172 + for _day, day_abs in day_dirs().items(): 173 + day_dir = Path(day_abs) 176 174 if not day_dir.is_dir(): 177 175 continue 178 176 for stream_dir in sorted(day_dir.iterdir()):
+2 -3
observe/export.py
··· 30 30 ) 31 31 from think.entities.journal import load_all_journal_entities 32 32 from think.importers.sync import SYNCABLE_REGISTRY 33 - from think.utils import get_config, get_journal, iter_segments, setup_cli 33 + from think.utils import day_path, get_config, get_journal, iter_segments, setup_cli 34 34 35 35 logger = logging.getLogger(__name__) 36 36 ··· 255 255 print(result.error) 256 256 return result 257 257 258 - journal = get_journal() 259 258 for day in days: 260 - day_dir = Path(journal) / day 259 + day_dir = day_path(day, create=False) 261 260 if not day_dir.exists(): 262 261 continue 263 262
+9 -14
observe/transfer.py
··· 29 29 import requests 30 30 31 31 from think.callosum import callosum_send 32 - from think.utils import get_journal, iter_segments, now_ms, setup_cli 32 + from think.utils import day_path, get_journal, iter_segments, now_ms, setup_cli 33 33 34 34 from .utils import compute_file_sha256, find_available_segment 35 35 ··· 93 93 Raises: 94 94 ValueError: If day directory doesn't exist or has no segments 95 95 """ 96 - journal = get_journal() 97 - day_dir = Path(journal) / day 96 + day_dir = day_path(day, create=False) 98 97 99 98 if not day_dir.exists(): 100 99 raise ValueError(f"Day directory does not exist: {day_dir}") ··· 230 229 manifest = _read_manifest(archive_path) 231 230 day = manifest["day"] 232 231 233 - journal = get_journal() 234 - day_dir = Path(journal) / day 232 + day_dir = day_path(day, create=False) 235 233 236 234 result = { 237 235 "manifest": manifest, ··· 311 309 } 312 310 313 311 # Ensure day directory exists 314 - journal = get_journal() 315 - day_dir = Path(journal) / day 316 - day_dir.mkdir(parents=True, exist_ok=True) 312 + day_dir = day_path(day) 317 313 318 314 # Extract segments 319 315 imported = [] ··· 395 391 start = datetime.strptime(start_str, "%Y%m%d") 396 392 end = datetime.strptime(end_str, "%Y%m%d") 397 393 if start > end: 398 - raise ValueError("Invalid day format: start day must be on or before end day") 394 + raise ValueError( 395 + "Invalid day format: start day must be on or before end day" 396 + ) 399 397 400 398 days = [] 401 399 current = start ··· 532 530 duplicates = 0 533 531 534 532 try: 535 - journal = get_journal() 536 533 for day in days: 537 - day_dir = Path(journal) / day 534 + day_dir = day_path(day, create=False) 538 535 if not day_dir.exists(): 539 536 logger.debug(f"Day directory not found: {day}") 540 537 continue ··· 661 658 ) 662 659 663 660 # Send subcommand 664 - send_parser = subparsers.add_parser( 665 - "send", help="Send segments to remote observer" 666 - ) 661 + send_parser = subparsers.add_parser("send", help="Send segments to remote observer") 667 662 send_parser.add_argument( 668 663 "--to", 669 664 required=True,
+2 -3
think/entities/activity.py
··· 18 18 from think.entities.loading import load_entities, parse_entity_file 19 19 from think.entities.matching import find_matching_entity 20 20 from think.entities.saving import save_entities 21 - from think.utils import get_journal 21 + from think.utils import day_path, get_journal 22 22 23 23 24 24 def touch_entity(facet: str, name: str, day: str) -> str: ··· 80 80 >>> parse_knowledge_graph_entities("20260108") 81 81 ["Jeremie Miller (Jer)", "Neal Satterfield", "Flightline", ...] 82 82 """ 83 - journal = get_journal() 84 - kg_path = Path(journal) / day / "agents" / "knowledge_graph.md" 83 + kg_path = day_path(day, create=False) / "agents" / "knowledge_graph.md" 85 84 86 85 if not kg_path.exists(): 87 86 return []
+2 -4
think/entities/context.py
··· 10 10 11 11 from __future__ import annotations 12 12 13 - from pathlib import Path 14 - 15 13 from think.entities.activity import parse_knowledge_graph_entities 16 14 from think.entities.loading import load_entities 17 15 from think.entities.matching import find_matching_entity 18 16 from think.entities.observations import load_observations 19 - from think.utils import get_journal 17 + from think.utils import day_path 20 18 21 19 22 20 def _active_entity_ids(facet: str, day: str, attached: list[dict]) -> set[str]: ··· 40 38 41 39 42 40 def _load_knowledge_graph(day: str) -> str: 43 - kg_path = Path(get_journal()) / day / "agents" / "knowledge_graph.md" 41 + kg_path = day_path(day, create=False) / "agents" / "knowledge_graph.md" 44 42 if not kg_path.exists(): 45 43 return "No knowledge graph available for this day." 46 44
+4 -3
think/journal_stats.py
··· 13 13 from observe.sense import scan_day as sense_scan_day 14 14 from observe.utils import VIDEO_EXTENSIONS, load_analysis_frames 15 15 from think.agents import scan_day as generate_scan_day 16 - from think.stats_schema import DAY_FIELDS, SCHEMA_VERSION, validate as validate_stats 16 + from think.stats_schema import DAY_FIELDS, SCHEMA_VERSION 17 + from think.stats_schema import validate as validate_stats 17 18 from think.utils import day_dirs, get_journal, setup_cli 18 19 19 20 logger = logging.getLogger(__name__) ··· 61 62 files.extend(agents_dir.glob("*/*.md")) 62 63 63 64 # Check facet event files for this day 64 - journal_root = day_dir.parent 65 + journal_root = Path(get_journal()) 65 66 day = day_dir.name 66 67 facets_dir = journal_root / "facets" 67 68 if facets_dir.is_dir(): ··· 269 270 270 271 # --- Events and heatmap from facets/*/events/YYYYMMDD.jsonl --- 271 272 weekday = datetime.strptime(day, "%Y%m%d").weekday() 272 - journal_root = day_dir.parent 273 + journal_root = Path(get_journal()) 273 274 facets_dir = journal_root / "facets" 274 275 275 276 if facets_dir.is_dir():
+9 -6
think/supervisor.py
··· 24 24 from think.runner import DailyLogWriter 25 25 from think.runner import ManagedProcess as RunnerManagedProcess 26 26 from think.utils import ( 27 + day_path, 27 28 find_available_port, 28 29 get_journal, 29 30 get_journal_info, ··· 142 143 on_queue_change: Optional callback(cmd_name, running_ref, queue_entries) 143 144 called after queue state changes. Called outside lock. 144 145 """ 145 - self._running: dict[str, dict] = {} # command_name -> {"ref": str, "thread": Thread} 146 + self._running: dict[ 147 + str, dict 148 + ] = {} # command_name -> {"ref": str, "thread": Thread} 146 149 self._queues: dict[str, list] = {} # command_name -> list of {refs, cmd} dicts 147 150 self._active: dict[str, RunnerManagedProcess] = {} # ref -> process 148 151 self._lock = threading.Lock() ··· 336 339 try: 337 340 callosum.stop() 338 341 except Exception: 339 - logging.exception(f"Task {cmd_name} ({primary_ref}): callosum stop failed") 342 + logging.exception( 343 + f"Task {cmd_name} ({primary_ref}): callosum stop failed" 344 + ) 340 345 self._process_next(cmd_name) 341 346 342 347 def _process_next(self, cmd_name: str) -> None: ··· 1142 1147 stream = message.get("stream") 1143 1148 1144 1149 try: 1145 - journal_path = _get_journal_path() 1146 - 1147 1150 if stream: 1148 - segment_dir = journal_path / day / stream / segment 1151 + segment_dir = day_path(day, create=False) / stream / segment 1149 1152 else: 1150 - segment_dir = journal_path / day / segment 1153 + segment_dir = day_path(day, create=False) / segment 1151 1154 1152 1155 # Only log if segment directory exists 1153 1156 if not segment_dir.is_dir():
+2 -2
think/talent_cli.py
··· 40 40 _load_prompt_metadata, 41 41 get_talent_configs, 42 42 ) 43 - from think.utils import setup_cli 43 + from think.utils import day_path, setup_cli 44 44 45 45 # Project root for computing relative paths 46 46 _PROJECT_ROOT = Path(__file__).parent.parent ··· 807 807 req_name = request_event.get("name", "unified") 808 808 req_env = request_event.get("env") or {} 809 809 req_stream = req_env.get("SOL_STREAM") if req_env else None 810 - day_dir = Path(journal_root) / req_day 810 + day_dir = day_path(req_day, create=False) 811 811 out_path = get_output_path( 812 812 day_dir, 813 813 req_name,
+10 -13
think/tools/call.py
··· 41 41 from think.indexer.journal import search_counts as search_counts_impl 42 42 from think.indexer.journal import search_journal as search_journal_impl 43 43 from think.utils import ( 44 + day_path, 44 45 get_journal, 45 46 iter_segments, 46 47 resolve_sol_day, ··· 589 590 """List available agent outputs for a day.""" 590 591 day = resolve_sol_day(day) 591 592 segment = resolve_sol_segment(segment) 592 - journal = get_journal() 593 - day_path = Path(journal) / day 593 + day_dir = day_path(day, create=False) 594 594 595 - if not day_path.is_dir(): 595 + if not day_dir.is_dir(): 596 596 typer.echo(f"No data for {day}.") 597 597 return 598 598 599 599 if segment: 600 600 # List outputs in a specific segment directory 601 - seg_path = day_path / segment / "agents" 601 + seg_path = day_dir / segment / "agents" 602 602 if not seg_path.is_dir(): 603 603 typer.echo(f"Segment {segment} not found for {day}.") 604 604 return ··· 606 606 return 607 607 608 608 # List daily agent outputs 609 - agents_path = day_path / "agents" 609 + agents_path = day_dir / "agents" 610 610 if agents_path.is_dir(): 611 611 _list_outputs(agents_path, "Daily agents") 612 612 ··· 671 671 """Read full content of an agent output.""" 672 672 day = resolve_sol_day(day) 673 673 segment = resolve_sol_segment(segment) 674 - journal = get_journal() 675 - day_path = Path(journal) / day 674 + day_dir = day_path(day, create=False) 676 675 677 - if not day_path.is_dir(): 676 + if not day_dir.is_dir(): 678 677 typer.echo(f"No data for {day}.", err=True) 679 678 raise typer.Exit(1) 680 679 681 680 if segment: 682 - base_dir = day_path / segment / "agents" 681 + base_dir = day_dir / segment / "agents" 683 682 else: 684 - base_dir = day_path / "agents" 683 + base_dir = day_dir / "agents" 685 684 686 685 if not base_dir.is_dir(): 687 686 location = f"segment {segment}" if segment else "agents" ··· 993 992 raise typer.Exit(1) 994 993 995 994 if mode is not None and mode not in ("keep", "days", "processed"): 996 - typer.echo( 997 - f"Invalid mode: {mode}. Must be keep, days, or processed.", err=True 998 - ) 995 + typer.echo(f"Invalid mode: {mode}. Must be keep, days, or processed.", err=True) 999 996 raise typer.Exit(1) 1000 997 1001 998 if mode == "days" and days is None:
+5 -10
think/tools/sol.py
··· 17 17 18 18 import typer 19 19 20 - from think.entities.core import atomic_write 21 20 from think.awareness import ( 22 21 _log_identity_change, 23 22 ensure_sol_directory, 24 23 update_identity_section, 25 24 update_self_md_section, 26 25 ) 26 + from think.entities.core import atomic_write 27 + from think.utils import day_dirs, day_path 27 28 28 29 app = typer.Typer( 29 30 help="Sol identity directory — self.md, partner.md, agency.md, pulse.md, awareness.md, and morning briefing." ··· 258 259 day: str | None = typer.Option(None, "--day", "-d", help="Specific day YYYYMMDD."), 259 260 ) -> None: 260 261 """Read the morning briefing from YYYYMMDD/agents/morning_briefing.md.""" 261 - from pathlib import Path as _Path 262 - 263 - from think.utils import get_journal 264 - 265 - journal = _Path(get_journal()) 266 - 267 262 if day: 268 - path = journal / day / "agents" / "morning_briefing.md" 263 + path = day_path(day, create=False) / "agents" / "morning_briefing.md" 269 264 if not path.exists(): 270 265 typer.echo("No briefing found.", err=True) 271 266 raise typer.Exit(1) ··· 273 268 return 274 269 275 270 # No day specified — find most recent 276 - agents_dirs = sorted(journal.glob("*/agents"), reverse=True) 277 - for agents_dir in agents_dirs: 271 + for day in sorted(day_dirs().keys(), reverse=True): 272 + agents_dir = day_path(day, create=False) / "agents" 278 273 briefing = agents_dir / "morning_briefing.md" 279 274 if briefing.exists() and briefing.stat().st_size > 0: 280 275 typer.echo(briefing.read_text(encoding="utf-8"))