personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3
4"""Tool functions for facet management.
5
6These functions can be imported and called directly from agent workflows,
7tests, or other internal modules.
8"""
9
10from pathlib import Path
11from typing import Any
12
13from think.facets import facet_summary
14from think.utils import get_journal
15
16
17def get_facet(facet: str) -> dict[str, Any]:
18 """Get a comprehensive summary of a facet including its metadata and entities.
19
20 This tool generates a formatted markdown summary for a specified facet in the journal.
21 The summary includes the facet's title, description, and tracked entities.
22 Use this when you need an overview of a facet's current state and its associated entities.
23
24 Args:
25 facet: The facet name to retrieve the summary for
26
27 Returns:
28 Dictionary containing:
29 - facet: The facet name that was queried
30 - summary: Formatted markdown text with the complete facet summary including:
31 - Facet title
32 - Facet description
33 - List of tracked entities
34
35 Examples:
36 - get_facet("personal")
37 - get_facet("work_projects")
38 - get_facet("research")
39
40 Returns:
41 If the facet doesn't exist, returns an error dictionary with an error message
42 and suggestion for resolution.
43 """
44 try:
45 # Get the facet summary markdown
46 summary_text = facet_summary(facet)
47 return {"facet": facet, "summary": summary_text}
48 except FileNotFoundError:
49 return {
50 "error": f"Facet '{facet}' not found",
51 "suggestion": "verify the facet name exists in the journal",
52 }
53 except Exception as exc:
54 return {
55 "error": f"Failed to get facet summary: {exc}",
56 "suggestion": "check that the facet exists and has valid metadata",
57 }
58
59
60def facet_news(facet: str, day: str, markdown: str | None = None) -> dict[str, Any]:
61 """Read or write news for a specific facet and day.
62
63 This tool manages facet-specific news stored in markdown files organized by date.
64 When markdown content is provided, it writes/updates the news file for that day.
65 When markdown is not provided, it reads and returns the existing news for that day.
66 News files are stored as `facets/<facet>/news/YYYYMMDD.md`.
67
68 Args:
69 facet: The facet name to manage news for
70 day: The day in YYYYMMDD format
71 markdown: Optional markdown content to write. If not provided, reads existing news.
72 Should follow the format with dated header and news entries with source/time.
73
74 Returns:
75 Dictionary containing either:
76 - facet, day, and news content when reading
77 - facet, day, and success message when writing
78 - error and suggestion if operation fails
79
80 Examples:
81 - facet_news("ml_research", "20250118") # Read news for the day
82 - facet_news("work", "20250118", "# 2025-01-18 News...") # Write news
83 """
84 try:
85 journal_path = Path(get_journal())
86 facet_path = journal_path / "facets" / facet
87
88 # Check if facet exists
89 if not facet_path.exists():
90 return {
91 "error": f"Facet '{facet}' not found",
92 "suggestion": "Create the facet first or check the facet name",
93 }
94
95 # Ensure news directory exists
96 news_dir = facet_path / "news"
97 news_dir.mkdir(exist_ok=True)
98
99 # Path to the specific day's news file
100 news_file = news_dir / f"{day}.md"
101
102 if markdown is not None:
103 # Write mode - save the markdown content
104 news_file.write_text(markdown, encoding="utf-8")
105 return {
106 "facet": facet,
107 "day": day,
108 "message": f"News for {day} saved successfully in facet '{facet}'",
109 }
110 else:
111 # Read mode - return existing news or empty message
112 if news_file.exists():
113 news_content = news_file.read_text(encoding="utf-8")
114 return {"facet": facet, "day": day, "news": news_content}
115 else:
116 return {
117 "facet": facet,
118 "day": day,
119 "news": None,
120 "message": f"No news recorded for {day} in facet '{facet}'",
121 }
122
123 except Exception as exc:
124 return {
125 "error": f"Failed to process facet news: {exc}",
126 "suggestion": "check facet exists and has proper permissions",
127 }