personal memory agent
0
fork

Configure Feed

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

Add discovery mode context injection and aggregate_speculative_facets()

When no facets are defined, the facet classifier now receives explicit discovery-mode
context instructing it to name observed contexts with specific, descriptive labels.
Previously it ran with zero facet context, producing unconstrained speculative output.

Also adds aggregate_speculative_facets() to think/facets.py, which scans per-segment
agents/facets.json files across days, counts facet name frequency, and returns the
top 8 by count with sample activities. Used by onboarding agents to suggest journal
organization to the user.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+86 -1
+77 -1
think/facets.py
··· 13 13 from typing import Any, Optional 14 14 15 15 from think.entities import get_identity_names 16 - from think.utils import day_path, get_journal, iter_segments 16 + from think.utils import DATE_RE, day_path, get_journal, iter_segments 17 17 18 18 19 19 def _get_principal_display_name() -> str | None: ··· 529 529 active.update(load_segment_facets(day, seg_key, stream=stream_name)) 530 530 531 531 return active 532 + 533 + 534 + def aggregate_speculative_facets(days: list[str] | None = None) -> list[dict]: 535 + """Aggregate speculative facet outputs from segment classifiers across days. 536 + 537 + Scans per-segment agents/facets.json files produced by the facets classifier 538 + and counts facet name frequency. Useful during onboarding to suggest journal 539 + organization to the user. 540 + 541 + Args: 542 + days: Optional list of days in YYYYMMDD format. If None, scans all days. 543 + 544 + Returns: 545 + List of dicts with keys: 546 + - "facet": facet name (str) 547 + - "count": number of segments where this facet appeared (int) 548 + - "sample_activities": up to 3 activity descriptions for this facet (list[str]) 549 + Sorted by count descending, capped at 8 entries. 550 + """ 551 + journal_path = Path(get_journal()) 552 + 553 + if days is not None: 554 + scan_days = days 555 + else: 556 + scan_days = [] 557 + if journal_path.exists(): 558 + for entry in sorted(journal_path.iterdir()): 559 + if entry.is_dir() and DATE_RE.fullmatch(entry.name): 560 + scan_days.append(entry.name) 561 + 562 + facet_counts: dict[str, int] = {} 563 + facet_activities: dict[str, list[str]] = {} 564 + 565 + for day in scan_days: 566 + for _stream, _seg_key, seg_path in iter_segments(day): 567 + facets_file = seg_path / "agents" / "facets.json" 568 + if not facets_file.exists(): 569 + continue 570 + 571 + try: 572 + content = facets_file.read_text().strip() 573 + if not content: 574 + continue 575 + 576 + data = json.loads(content) 577 + if not isinstance(data, list): 578 + continue 579 + 580 + for item in data: 581 + if not isinstance(item, dict): 582 + continue 583 + facet_name = item.get("facet") 584 + if not facet_name: 585 + continue 586 + facet_counts[facet_name] = facet_counts.get(facet_name, 0) + 1 587 + 588 + activity = item.get("activity", "") 589 + if activity: 590 + samples = facet_activities.setdefault(facet_name, []) 591 + if len(samples) < 3: 592 + samples.append(activity) 593 + 594 + except (json.JSONDecodeError, OSError): 595 + continue 596 + 597 + result = [ 598 + { 599 + "facet": facet_name, 600 + "count": count, 601 + "sample_activities": facet_activities.get(facet_name, []), 602 + } 603 + for facet_name, count in sorted( 604 + facet_counts.items(), key=lambda x: x[1], reverse=True 605 + ) 606 + ] 607 + return result[:8] 532 608 533 609 534 610 def set_facet_muted(facet: str, muted: bool) -> None:
+9
think/muse.py
··· 496 496 summary = facet_summaries() 497 497 if summary and summary != "No facets found.": 498 498 extra_parts.append(summary) 499 + else: 500 + extra_parts.append( 501 + "No facets are defined yet. You are in discovery mode. " 502 + "Name the contexts you observe based on what is actually happening " 503 + "in this segment \u2014 use specific, descriptive names that reflect the " 504 + 'actual activity (e.g., "engineering-work" not "work", ' 505 + '"investor-calls" not "meetings"). These names will be used to ' 506 + "suggest journal organization to the user." 507 + ) 499 508 except Exception: 500 509 pass # Ignore if facets can't be loaded 501 510