personal memory agent
0
fork

Configure Feed

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

feat(activities): add daily activities cogitate talent with $facet and $activity_md_dir vars

Sprint 2 Part B (req_izc53wlf, founder-approved 2026-04-18). Adds the daily
cogitate talent that reads per-span narrative .md files and creates missed
activity records (in-person meetings, phone calls, brief interactions).

Wires two new template vars into _build_prompt_context:
- $facet: bare facet name (e.g., \"work\")
- $activity_md_dir: {journal}/facets/{facet}/activities/{day}/

Both vars are populated only when a facet is dispatched with a day, matching
production semantics. The existing $facets (plural rich markdown) variable is
unchanged.

The talent slots in at priority 30, between events agents (priority 10) and
entity analysis (priority 40+). multi_facet: true fans out per active facet.

Note: the founder-approved prompt instructs the talent to include a
`participation` field on `sol call activities create` payloads, but the CLI
does not currently ingest that field — it falls through silently to
`active_entities: []`. Tracked as a follow-up; prompt body intentionally
matches the approved source verbatim.

Co-Authored-By: OpenAI Codex <codex@openai.com>

+150 -1
+44
apps/activities/talent/activities_review.md
··· 1 + { 2 + "type": "cogitate", 3 + "title": "Activities Review", 4 + "description": "Daily review for missed in-person meetings, calls, and brief interactions.", 5 + "color": "#00695c", 6 + "schedule": "daily", 7 + "priority": 30, 8 + "multi_facet": true, 9 + "tier": 3, 10 + "group": "Activities" 11 + } 12 + 13 + You are the activities tender for $day ($facet facet). Your job: find key, 14 + notable, or important activities that happened today but are missing from the 15 + activity records. Add them. 16 + 17 + ## Inputs 18 + 19 + 1. Existing activities — run `sol call activities list --facet $facet 20 + --day $day --json` to see what's already recorded. 21 + 22 + 2. Per-span narratives — the directory $activity_md_dir contains per-span 23 + subdirectories with .md files (meetings.md, decisions.md, followups.md, 24 + messaging.md) describing what happened during each captured span. Read these 25 + to understand the day's narrative. 26 + 27 + ## Your task 28 + 29 + Compare. If the per-span narratives describe an activity that doesn't have its 30 + own record — typically an in-person meeting, a phone call, a brief interaction 31 + not captured by desktop recording — add it. 32 + 33 + Use `sol call activities create` to add a record. Include: 34 + - activity type (meeting, call, etc.) 35 + - a starting segment (the closest real captured segment in time) 36 + - description (one-sentence prose summary) 37 + - participation (array of {name, role, source, confidence, context}) 38 + - source: "cogitate" 39 + 40 + JSON payload note: `title` is required; `details` is optional. 41 + 42 + Quality bar: only add activities that are key, notable, or important. A passing 43 + mention of "I need to call Dennis later" is not a missed activity. A meeting 44 + described in detail with multiple participants IS a missed activity.
+2
docs/PROMPT_TEMPLATES.md
··· 86 86 **Common generator context:** 87 87 - `$day` - Human-readable date (e.g., "Friday, January 24, 2026") 88 88 - `$day_YYYYMMDD` - Day in YYYYMMDD format (e.g., "20260124") 89 + - `$facet` - Focused facet name when the prompt is dispatched per-facet (bare name, e.g. `work`) 90 + - `$activity_md_dir` - Directory path containing per-activity narrative `.md` outputs for the focused facet/day, with trailing slash 89 91 - `$now` - Current date and time with timezone (e.g., "Monday, February 3, 2025 at 10:30 AM PST") 90 92 - `$segment` - Segment key (e.g., "143022_300") 91 93 - `$segment_start` - Formatted start time (e.g., "2:30 PM")
+8
tests/baselines/api/settings/providers.json
··· 102 102 "label": "Agent Prompt Generation", 103 103 "tier": 2 104 104 }, 105 + "talent.activities.activities_review": { 106 + "disabled": false, 107 + "group": "Activities", 108 + "label": "Activities Review", 109 + "schedule": "daily", 110 + "tier": 3, 111 + "type": "cogitate" 112 + }, 105 113 "talent.entities.entities": { 106 114 "disabled": false, 107 115 "group": "Entities",
+11
tests/baselines/api/sol/talents-day.json
··· 1 1 { 2 2 "talents": { 3 + "activities:activities_review": { 4 + "app": "activities", 5 + "color": "#00695c", 6 + "description": "Daily review for missed in-person meetings, calls, and brief interactions.", 7 + "multi_facet": true, 8 + "output_format": null, 9 + "schedule": "daily", 10 + "source": "app", 11 + "title": "Activities Review", 12 + "type": "cogitate" 13 + }, 3 14 "anticipation": { 4 15 "app": null, 5 16 "color": "#4527a0",
+67
tests/test_think_activity.py
··· 992 992 # Invalid segment can't parse, should default to 1 993 993 assert ctx["activity_duration"] == "1" 994 994 995 + def test_facet_and_activity_md_dir_populated(self, monkeypatch, tmp_path): 996 + from think.talents import _build_prompt_context 997 + 998 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 999 + 1000 + ctx = _build_prompt_context( 1001 + day="20260418", 1002 + segment=None, 1003 + span=None, 1004 + facet="work", 1005 + ) 1006 + 1007 + assert ctx["facet"] == "work" 1008 + assert ctx["activity_md_dir"] == f"{tmp_path}/facets/work/activities/20260418/" 1009 + 1010 + def test_activity_md_dir_omitted_without_facet(self): 1011 + from think.talents import _build_prompt_context 1012 + 1013 + ctx = _build_prompt_context(day="20260418", segment=None, span=None) 1014 + 1015 + assert "facet" not in ctx 1016 + assert "activity_md_dir" not in ctx 1017 + 1018 + 1019 + def test_prepare_config_substitutes_facet_and_activity_md_dir_for_daily_cogitate( 1020 + tmp_path, monkeypatch 1021 + ): 1022 + import importlib 1023 + 1024 + import think.talent 1025 + from think.utils import day_path 1026 + 1027 + mod = importlib.import_module("think.talents") 1028 + 1029 + monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 1030 + day_dir = day_path("20260418") 1031 + day_dir.mkdir(parents=True, exist_ok=True) 1032 + 1033 + monkeypatch.setattr(think.talent, "TALENT_DIR", tmp_path) 1034 + 1035 + test_agent = tmp_path / "test_activities_cogitate.md" 1036 + test_agent.write_text( 1037 + "{\n" 1038 + ' "type": "cogitate",\n' 1039 + ' "schedule": "daily",\n' 1040 + ' "priority": 30,\n' 1041 + ' "multi_facet": true\n' 1042 + "}\n\n" 1043 + "Facet: $facet\n" 1044 + "Dir: $activity_md_dir\n" 1045 + ) 1046 + 1047 + monkeypatch.setenv("GOOGLE_API_KEY", "x") 1048 + 1049 + config = mod.prepare_config( 1050 + { 1051 + "name": "test_activities_cogitate", 1052 + "day": "20260418", 1053 + "facet": "work", 1054 + } 1055 + ) 1056 + 1057 + assert "Facet: work" in config["user_instruction"] 1058 + assert "facets/work/activities/20260418/" in config["user_instruction"] 1059 + assert "$facet" not in config["user_instruction"] 1060 + assert "$activity_md_dir" not in config["user_instruction"] 1061 + 995 1062 996 1063 # --------------------------------------------------------------------------- 997 1064 # Talent config validation for activity schedule
+18 -1
think/talents.py
··· 211 211 segment: str | None, 212 212 span: list[str] | None, 213 213 activity: dict | None = None, 214 + facet: str | None = None, 214 215 ) -> dict[str, str]: 215 216 """Build context dict for prompt template substitution. 216 217 ··· 219 220 segment: Segment key (HHMMSS_LEN) 220 221 span: List of segment keys 221 222 activity: Optional activity record dict for activity-scheduled talents 223 + facet: Optional facet name for daily multi-facet talents 222 224 223 225 Returns: 224 226 Dict with template variables: ··· 227 229 - segment_start, segment_end: Time strings if segment/span provided 228 230 - stream, content_description: Stream name and human-readable description 229 231 - activity_*: Activity fields if activity record provided 232 + - facet, activity_md_dir: Facet name and activity markdown dir for daily runs 230 233 """ 231 234 context: dict[str, str] = {} 232 235 if not day: ··· 281 284 segments = activity.get("segments", []) 282 285 context["activity_segments"] = ", ".join(segments) if segments else "" 283 286 context["activity_duration"] = str(estimate_duration_minutes(segments)) 287 + 288 + if facet: 289 + context["facet"] = facet 290 + try: 291 + context["activity_md_dir"] = ( 292 + f"{get_journal()}/facets/{facet}/activities/{day}/" 293 + ) 294 + except Exception: 295 + LOG.debug( 296 + "Failed to build activity_md_dir for facet=%s day=%s", 297 + facet, 298 + day, 299 + exc_info=True, 300 + ) 284 301 285 302 return context 286 303 ··· 578 595 from think.prompts import _resolve_facets 579 596 580 597 prompt_context = _build_prompt_context( 581 - day, segment, span, activity=activity 598 + day, segment, span, activity=activity, facet=facet 582 599 ) 583 600 prompt_context["facets"] = _resolve_facets(facet) 584 601