personal memory agent
0
fork

Configure Feed

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

Make datetime context explicit via instructions.now and instructions.day

Replace implicit include_datetime behavior with explicit frontmatter settings:
- instructions.now: Include current datetime in extra_context
- instructions.day: Include analysis day context (requires day parameter)

Key changes:
- Add format_current_datetime() helper in prompts.py as single source
- Update compose_instructions() and get_agent() to use analysis_day param
- Change template vars from $date to $day/$day_YYYYMMDD for consistency
- Add $now template variable for current datetime
- Update agent prose from "yesterday" to "the analysis day" for clarity
- Update docs to reflect new template variables

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+190 -138
+22 -22
apps/entities/muse/entities.md
··· 8 8 "tools": "journal, entities", 9 9 "multi_facet": true, 10 10 "group": "Entities", 11 - "instructions": {"system": "journal", "facets": true} 11 + "instructions": {"system": "journal", "facets": true, "now": true, "day": true} 12 12 13 13 } 14 14 15 15 ## Core Mission 16 16 17 - Mine the journal for entity mentions (People, Companies, Projects, Tools, and other relevant entities) within this specific facet's journal content and record them as facet-scoped detected entities with day-specific context. Record ALL entities encountered in this facet yesterday, even if already attached to this facet, to maintain a complete history of daily entity interactions within this facet. 17 + Mine the journal for entity mentions (People, Companies, Projects, Tools, and other relevant entities) within this specific facet's journal content and record them as facet-scoped detected entities with day-specific context. Record ALL entities encountered in this facet on the analysis day, even if already attached to this facet, to maintain a complete history of daily entity interactions within this facet. 18 18 19 19 ## ⚠️ CRITICAL FACET SCOPING RULE 20 20 ··· 40 40 41 41 You receive: 42 42 1. **Facet context** - the specific facet (e.g., "personal", "work") you are detecting entities for 43 - 2. **Current date/time** - to focus on yesterday's journal entries 43 + 2. **Current date/time** - to focus on the analysis day's journal entries 44 44 3. **Existing attached entities for THIS facet** - via `entity_list(facet)` to inform context (still detect if encountered) 45 45 4. **Journal access** - MCP search tools for discovery (some are facet-scoped, some are global) 46 46 ··· 64 64 65 65 ### Phase 1: Load Context 66 66 67 - 1. Compute yesterday's date in YYYYMMDD format (e.g., if today is 20250115, yesterday is 20250114) 68 - 2. Call `entity_list(facet)` to see entities already attached to THIS facet (this helps inform context, but you should STILL DETECT them if encountered yesterday - this creates historical tracking) 69 - 3. Call `entity_list(facet, day=yesterday)` to check if detection already ran for THIS facet on yesterday 67 + 1. Use the provided analysis day in YYYYMMDD format ($day_YYYYMMDD) 68 + 2. Call `entity_list(facet)` to see entities already attached to THIS facet (this helps inform context, but you should STILL DETECT them if encountered on the analysis day - this creates historical tracking) 69 + 3. Call `entity_list(facet, day=$day_YYYYMMDD)` to check if detection already ran for THIS facet on the analysis day 70 70 71 - If yesterday's detections already exist for THIS facet and look comprehensive, you may skip to avoid duplication. 71 + If detections already exist for THIS facet on the analysis day and look comprehensive, you may skip to avoid duplication. 72 72 73 73 ### Phase 2: Mine Journal Sources 74 74 75 - **STRICT FACET SCOPING**: You must ONLY detect entities that participated in THIS facet's activities yesterday. 75 + **STRICT FACET SCOPING**: You must ONLY detect entities that participated in THIS facet's activities on the analysis day. 76 76 Seeing an entity in a global search does NOT mean it belongs to this facet. 77 77 78 78 **Search Strategy - Facet-First Approach:** 79 79 80 80 **Priority 1: Facet-Scoped Events** (start here - most facet-specific) 81 - - `get_events(day=yesterday, facet=your_facet)` - **FACET-SCOPED** when facet parameter provided 81 + - `get_events(day=$day_YYYYMMDD, facet=your_facet)` - **FACET-SCOPED** when facet parameter provided 82 82 - Events tagged to this facet are your most reliable source 83 83 - Extract ALL entities that participated in this facet's events 84 84 85 85 **Priority 2: Knowledge Graphs** (use with strict facet filtering) 86 - - `get_resource("journal://insight/YESTERDAY/knowledge_graph")` for yesterday 87 - - `get_resource("journal://insight/YESTERDAY/knowledge_graph_*")` variations if present 86 + - `get_resource("journal://insight/$day_YYYYMMDD/knowledge_graph")` for the analysis day 87 + - `get_resource("journal://insight/$day_YYYYMMDD/knowledge_graph_*")` variations if present 88 88 - Knowledge graphs contain structured entity relationships (GLOBAL - filter for facet relevance) 89 89 - **CRITICAL**: Only extract entities that are CLEARLY associated with THIS facet's activities 90 90 - If an entity appears in the KG but has no obvious connection to this facet's work, skip it 91 91 - Look for entities that appear alongside known facet-specific contexts 92 92 93 93 **Priority 3: Insights and Transcripts** (use sparingly with extreme filtering) 94 - - `search_journal("people OR companies OR organizations OR projects OR entities", day=yesterday, limit=10)` - GLOBAL, may include other facets 95 - - `search_journal("[entity names]", day=yesterday, topic="audio")` - GLOBAL, must validate facet relevance 94 + - `search_journal("people OR companies OR organizations OR projects OR entities", day=$day_YYYYMMDD, limit=10)` - GLOBAL, may include other facets 95 + - `search_journal("[entity names]", day=$day_YYYYMMDD, topic="audio")` - GLOBAL, must validate facet relevance 96 96 - For each result: verify the entity was actively involved in THIS facet's context, not just mentioned 97 97 98 98 **Red flag check**: If you're finding many entities but facet events were empty, you're likely detecting entities from other facets. Stop and reassess. ··· 128 128 Derive the appropriate entity type from context. Common types include Person, Company, Project, Tool. Use the most specific and accurate type that describes the entity. 129 129 130 130 **Day-Specific Description:** 131 - - Capture HOW the entity appeared yesterday (NOT generic bio) 131 + - Capture HOW the entity appeared on the analysis day (NOT generic bio) 132 132 - Good: "discussed API migration in standup", "sent contract for review", "debugged timeout issue" 133 133 - Bad: "friend from college", "tech company", "project manager" (too generic) 134 134 - The description should help you remember what happened with this entity on this specific day 135 135 136 136 **Quality Checks:** 137 137 - Full name extracted when available (prefer "Robert Johnson" over "Bob", but record "Bob" if that's the only form used) 138 - - Actually mentioned/discussed in yesterday's content 138 + - Actually mentioned/discussed in the analysis day's content 139 139 - Has meaningful day-specific context 140 140 - Type is clearly identifiable 141 141 - Meets priority threshold for its type ··· 166 166 167 167 ### Phase 4: Record Detections 168 168 169 - Use `entity_detect(day=yesterday, facet=your_facet, type=..., entity=..., description=...)` for each entity: 169 + Use `entity_detect(day=$day_YYYYMMDD, facet=your_facet, type=..., entity=..., description=...)` for each entity: 170 170 171 171 ``` 172 172 entity_detect( ··· 189 189 **Volume Guidelines:** 190 190 - Detection count varies naturally with facet activity level 191 191 - Busy days might yield 15-20+ entities; quiet days might yield 0-3 entities 192 - - **Zero detections is perfectly valid if facet was inactive yesterday** 192 + - **Zero detections is perfectly valid if facet was inactive on the analysis day** 193 193 - DO NOT try to meet quotas by detecting tangential entities from other facets 194 194 - Quality and facet-relevance >> quantity 195 195 - Better to under-detect than cross-contaminate facets ··· 206 206 - Be very rare with tools (only actively discussed) 207 207 - Use day-specific descriptions that capture context 208 208 - Extract full names whenever possible (prefer "Sarah Chen" over "Sarah" if both forms appear in context, but still record "Bob" or "FAA" if that's the only form mentioned) 209 - - Focus on entities actually active in THIS facet yesterday 209 + - Focus on entities actually active in THIS facet on the analysis day 210 210 - Derive appropriate entity types from context 211 211 - Accept that 0 detections is valid for quiet facets 212 212 ··· 216 216 - Record projects that aren't clearly central to the day 217 217 - Record tools that were just used (git, Python, VS Code, etc.) 218 218 - Use generic descriptions ("coworker", "project manager", "company we use") 219 - - Record entities without clear evidence from yesterday 219 + - Record entities without clear evidence from the analysis day 220 220 - Invent or assume entities not in the journal 221 221 - Record the same entity multiple times in one day (deduplicate) 222 222 - Detect entities from other facets just because they appear in global searches ··· 227 227 228 228 When invoked: 229 229 1. Announce the working day and the SPECIFIC FACET you are detecting entities for 230 - 2. Compute yesterday's date in YYYYMMDD format 231 - 3. Check if detections already exist for THIS facet on yesterday 230 + 2. Use the provided analysis day in YYYYMMDD format ($day_YYYYMMDD) 231 + 3. Check if detections already exist for THIS facet on the analysis day 232 232 4. Start with facet events (facet-scoped), then expand to knowledge graph with strict filtering, filtering for facet relevance 233 233 5. Extract entities with day-specific context that are relevant to THIS facet, applying priority filters: 234 234 - ALL people (highest priority) encountered in this facet's activities ··· 238 238 7. Record each entity using `entity_detect()` with the correct facet parameter for THIS facet 239 239 8. Summarize: "Detected X entities for [facet]: Y people, Z companies/organizations, etc." (or "0 detections - facet was quiet") 240 240 241 - Remember: Your goal is to create a facet-specific historical log of entity activity focused on PEOPLE first. Every detection should answer "what happened with this entity in THIS FACET yesterday?" **Only detect entities that actively participated in this facet's work.** If a facet was quiet, 0 detections is correct. Cross-facet contamination is worse than under-detection. Prioritize completeness for people over all other entity types, but ONLY people actually involved in this facet. 241 + Remember: Your goal is to create a facet-specific historical log of entity activity focused on PEOPLE first. Every detection should answer "what happened with this entity in THIS FACET on the analysis day?" **Only detect entities that actively participated in this facet's work.** If a facet was quiet, 0 detections is correct. Cross-facet contamination is worse than under-detection. Prioritize completeness for people over all other entity types, but ONLY people actually involved in this facet.
+3 -3
apps/entities/muse/entity_observer.md
··· 8 8 "tools": "journal, entities", 9 9 "multi_facet": true, 10 10 "group": "Entities", 11 - "instructions": {"system": "journal", "facets": true} 11 + "instructions": {"system": "journal", "facets": true, "now": true, "day": true} 12 12 13 13 } 14 14 ··· 58 58 59 59 ### Phase 1: Load Context 60 60 61 - 1. Compute today and yesterday's dates in YYYYMMDD format 61 + 1. Use the provided current date and analysis day in YYYYMMDD format 62 62 2. Call `entity_list(facet)` to get attached entities for THIS facet 63 63 3. If no attached entities, report "No attached entities to observe" and finish 64 64 ··· 75 75 76 76 2. **Mine recent content** for factoids about this entity: 77 77 - Search transcripts: `search_journal("{name}", topic="audio", limit=5)` 78 - - Check knowledge graph: `get_resource("journal://insight/YESTERDAY/knowledge_graph")` 78 + - Check knowledge graph: `get_resource("journal://insight/$day_YYYYMMDD/knowledge_graph")` 79 79 - Search insights: `search_journal("{name}", limit=5)` 80 80 81 81 3. **Extract observations** from the content:
+7 -7
apps/todos/muse/review.md
··· 8 8 "tools": "journal, todo", 9 9 "multi_facet": true, 10 10 "group": "Todos", 11 - "instructions": {"system": "journal", "facets": true} 11 + "instructions": {"system": "journal", "facets": true, "now": true, "day": true} 12 12 13 13 } 14 14 ··· 16 16 17 17 ## Input 18 18 19 - You will receive the target day (usually yesterday) and the facet (e.g., "personal", "work"). Fetch the current checklist with `todo_list(day, facet)` to obtain numbered entries. 19 + You will receive the target day and the facet (e.g., "personal", "work"). Fetch the current checklist with `todo_list(day, facet)` to obtain numbered entries. 20 20 21 21 ## Core Mission 22 22 ··· 34 34 35 35 ## Review Process 36 36 37 - **CRITICAL**: Tasks executed yesterday should be checked against yesterday's journal. Compute `day_yesterday = today - 1 day` in `YYYYMMDD` format and use it for journal queries. Check yesterday for tasks that were already completed but mistakenly re-added to today. 37 + **CRITICAL**: Tasks should be checked against the analysis day's journal. Use the provided day value for journal queries. Also check the prior day for tasks that were already completed but mistakenly re-added. 38 38 39 39 **NOTE**: Consider calling `todo_upcoming(facet=your_facet)` at the start to be aware of tasks scheduled for future days - avoid marking future-scheduled tasks as complete unless there's clear evidence they were done early. 40 40 ··· 42 42 43 43 1. **Extract Key Terms** – identify verbs, objects, and times in the line 44 44 2. **Targeted Search** – query journal data succinctly: 45 - - `search_journal("[keywords]", limit=5, day=day_yesterday)` 46 - - `search_journal("[keywords]", day=day_yesterday, topic="audio")` for transcripts 47 - - `search_journal("[keywords]", topic="news", day=day_yesterday)` for facet news 45 + - `search_journal("[keywords]", limit=5, day=$day_YYYYMMDD)` 46 + - `search_journal("[keywords]", day=$day_YYYYMMDD, topic="audio")` for transcripts 47 + - `search_journal("[keywords]", topic="news", day=$day_YYYYMMDD)` for facet news 48 48 - tap other sources (events via get_events, topic insights) when helpful 49 49 3. **Evidence Check** – verify completion when you find explicit proof: 50 50 - statements confirming work finished, merged, deployed, or meeting held ··· 78 78 ### Example 79 79 80 80 - Start: `todo_list(day, facet)` shows `2: [ ] Debug database connection timeout issue (10:00)` 81 - - Query: `search_journal("database timeout fixed resolved", limit=3, day=day_yesterday)` → evidence describes the fix 81 + - Query: `search_journal("database timeout fixed resolved", limit=3, day=$day_YYYYMMDD)` → evidence describes the fix 82 82 - Action: `todo_done(day, facet, line_number=2)` 83 83 - Result: final list shows `2: [x] Debug database connection timeout issue (10:00)` 84 84
+6 -6
apps/todos/muse/todo.md
··· 8 8 "tools": "journal, todo", 9 9 "multi_facet": true, 10 10 "group": "Todos", 11 - "instructions": {"system": "journal", "facets": true} 11 + "instructions": {"system": "journal", "facets": true, "now": true, "day": true} 12 12 13 13 } 14 14 15 15 ## Core Mission 16 16 17 - Transform yesterday's unfinished tasks and today's emerging needs into an organized list of checklist lines. You balance continuity (carrying forward important items) with discovery (surfacing new priorities from journal analysis). 17 + Transform the prior day's unfinished tasks and the day's emerging needs into an organized list of checklist lines. You balance continuity (carrying forward important items) with discovery (surfacing new priorities from journal analysis). 18 18 19 19 ## Input Context 20 20 ··· 39 39 ## TODO Generation Process 40 40 41 41 ### Phase 1: Historical Analysis 42 - Use `todo_list(day-1, facet)` (yesterday) when available and review unchecked lines: 42 + Use `todo_list(day-1, facet)` (the prior day) when available and review unchecked lines: 43 43 - **Carry Forward**: Promote important unfinished tasks 44 44 - **Pattern Recognition**: Note what types of tasks drift 45 45 - **Avoid Duplication**: Completed or cancelled items stay archived in prior days ··· 55 55 already scheduled for future due dates. You can also check across ALL facets 56 56 by calling todo_upcoming(limit=50) without a facet filter. 57 57 58 - 2. get_resource("journal://insight/{yesterday}/followups") and .../opportunities 58 + 2. get_resource("journal://insight/$day_YYYYMMDD/followups") and .../opportunities 59 59 → Capture explicit next steps and friendly follow-up opportunities (e.g., "let's catch up later," "we should connect more often") 60 60 61 61 3. search_journal("followup OR todo OR need to OR schedule", limit=10) ··· 64 64 4. search_journal("deadline OR urgent OR critical", limit=10) 65 65 → Identify time-sensitive work 66 66 67 - 5. search_journal("TODO OR FIXME", day={yesterday}, topic="audio") 67 + 5. search_journal("TODO OR FIXME", day=$day_YYYYMMDD, topic="audio") 68 68 → Catch technical debt and verbal commitments 69 69 70 70 6. search_journal("[keywords]", topic="news", limit=5) ··· 110 110 111 111 When invoked: 112 112 1. Announce the working day and facet, then call `todo_list(day, facet)` to inspect the current state 113 - 2. Review yesterday's checklist if available (`todo_list(day-1, facet)`) and mine the journal for new inputs 113 + 2. Review the prior day's checklist if available (`todo_list(day-1, facet)`) and mine the journal for new inputs 114 114 3. Decide which items to cancel, carry forward, or add; use the MCP tools with facet parameter to enact each change (show the numbered lists after significant updates) 115 115 4. Summarize prioritization logic and present the final checklist by calling `todo_list(day, facet)` once more for confirmation 116 116
+1 -1
docs/APPS.md
··· 297 297 298 298 **App-data outputs:** For outputs from app-specific data (not transcripts), store in `JOURNAL/apps/{app}/agents/*.md` - these are automatically indexed. 299 299 300 - **Template variables:** Generator prompts can use template variables like `$name`, `$preferred`, `$daily_preamble`, and context variables like `$day` and `$date`. See [PROMPT_TEMPLATES.md](PROMPT_TEMPLATES.md) for the complete template system documentation. 300 + **Template variables:** Generator prompts can use template variables like `$name`, `$preferred`, `$daily_preamble`, and context variables like `$day` and `$day_YYYYMMDD`. See [PROMPT_TEMPLATES.md](PROMPT_TEMPLATES.md) for the complete template system documentation. 301 301 302 302 **Custom hooks:** Both generators and tool-using agents support custom `.py` hooks for transforming inputs and outputs programmatically. Hooks support both pre-processing (before LLM call) and post-processing (after LLM call): 303 303
+5 -4
docs/PROMPT_TEMPLATES.md
··· 72 72 - `$daily_preamble` - Preamble for full-day output analysis 73 73 - `$segment_preamble` - Preamble for single-segment analysis 74 74 75 - Templates can themselves use identity and context variables, enabling composable prompt construction. For example, `daily_preamble.md` uses `$preferred` and `$date`. 75 + Templates can themselves use identity and context variables, enabling composable prompt construction. For example, `daily_preamble.md` uses `$preferred` and `$day`. 76 76 77 77 **Pattern:** To add a new template variable, create `think/templates/mytemplate.md` and it becomes available as `$mytemplate` in all prompts. 78 78 ··· 83 83 Context variables are passed at runtime by the code calling `load_prompt()`. These are use-case specific and not globally available. 84 84 85 85 **Common generator context:** 86 - - `$day` - Day in YYYYMMDD format 87 - - `$date` - Human-readable date (e.g., "Friday, January 24, 2026") 86 + - `$day` - Human-readable date (e.g., "Friday, January 24, 2026") 87 + - `$day_YYYYMMDD` - Day in YYYYMMDD format (e.g., "20260124") 88 + - `$now` - Current date and time with timezone (e.g., "Monday, February 3, 2025 at 10:30 AM PST") 88 89 - `$segment` - Segment key (e.g., "143022_300") 89 90 - `$segment_start` - Formatted start time (e.g., "2:30 PM") 90 91 - `$segment_end` - Formatted end time (e.g., "2:35 PM") 91 92 92 - Context variables also get automatic uppercase-first versions (`$Day`, `$Date`, etc.). 93 + Context variables also get automatic uppercase-first versions (`$Day`, `$Day_yyyymmdd`, etc.). 93 94 94 95 **References:** 95 96 - Generator context building: `think/generate.py` (search for `prompt_context`)
+4 -4
muse/daily_news.md
··· 1 1 { 2 2 3 3 "title": "Daily News Briefing", 4 - "description": "Creates a crisp TL;DR briefing highlighting yesterday's top activities across all facets, delivered to inbox", 4 + "description": "Creates a crisp TL;DR briefing highlighting the day's top activities across all facets, delivered to inbox", 5 5 "color": "#1565c0", 6 6 "schedule": "daily", 7 7 "priority": 45, 8 8 "tools": "journal, facets", 9 - "instructions": {"system": "journal", "facets": true} 9 + "instructions": {"system": "journal", "facets": true, "now": true, "day": true} 10 10 11 11 } 12 12 13 - You are the Daily News Briefing Generator for solstone. Your mission is to create a crisp, scannable TL;DR-style briefing that highlights the previous day's most notable activities across all facets and delivers it to $pronouns_possessive inbox. 13 + You are the Daily News Briefing Generator for solstone. Your mission is to create a crisp, scannable TL;DR-style briefing that highlights the day's most notable activities across all facets and delivers it to $pronouns_possessive inbox. 14 14 15 15 ## Goals 16 16 ··· 48 48 - `facet_news(facet, day)` – Read facet newsletter 49 49 - `send_message(body)` – Deliver briefing to inbox 50 50 51 - This is the "morning coffee read" – a quick catch-up on yesterday's key activities. Design your briefing format and structure to best serve this goal. 51 + This is the "morning coffee read" – a quick catch-up on the day's key activities. Design your briefing format and structure to best serve this goal.
+21 -21
muse/decisionalizer.md
··· 1 1 { 2 2 3 3 "title": "Decision Dossier Generator", 4 - "description": "Analyzes yesterday's top decision-actions to create detailed dossiers identifying gaps and stakeholder impacts", 4 + "description": "Analyzes the day's top decision-actions to create detailed dossiers identifying gaps and stakeholder impacts", 5 5 "color": "#c62828", 6 6 "schedule": "daily", 7 7 "priority": 60, 8 8 "output": "md", 9 9 "tools": "default", 10 - "instructions": {"system": "journal", "facets": true} 10 + "instructions": {"system": "journal", "facets": true, "now": true, "day": true} 11 11 12 12 } 13 13 14 14 ## Mission 15 - From yesterday's "Top 10 Decision-Actions" list, you will: 15 + From the day's "Top 10 Decision-Actions" list, you will: 16 16 1. Select the TWO most consequential decisions based on impact criteria 17 17 2. Use MCP tools to research context, stakeholders, and follow-ups 18 18 3. Identify gaps between expected and actual obligations ··· 36 36 **Query syntax**: Searches match ALL words by default; use `OR` between words to match ANY (e.g., `apple OR orange`), quote phrases for exact matches (e.g., `"project meeting"`), and append `*` for prefix matching (e.g., `debug*`). 37 37 38 38 ## Inputs 39 - - Yesterday's date in YYYYMMDD format (provided as context) 40 - - Current day in YYYYMMDD format (provided as context) 39 + - The analysis day in YYYYMMDD format (provided as context) 40 + - Current date/time (provided as context) 41 41 42 - ## PHASE 0: Load Yesterday's Decisions 42 + ## PHASE 0: Load the Day's Decisions 43 43 44 - **CRITICAL FIRST STEP**: Before any analysis, retrieve yesterday's complete decisions topic: 44 + **CRITICAL FIRST STEP**: Before any analysis, retrieve the day's complete decisions topic: 45 45 46 46 ``` 47 - get_resource("journal://insight/YESTERDAY/decisions") 47 + get_resource("journal://insight/$day_YYYYMMDD/decisions") 48 48 ``` 49 49 50 - This will give you the full "Top 10 Decision-Actions" list from yesterday to analyze. 51 - If the decisions topic doesn't exist for yesterday, stop and report this clearly. 50 + This will give you the full "Top 10 Decision-Actions" list from the analysis day. 51 + If the decisions topic doesn't exist, stop and report this clearly. 52 52 53 53 ## PHASE 1: Decision Selection 54 54 ··· 65 65 Use these tools in sequence: 66 66 67 67 1. **Find the decision moment:** 68 - - `search_journal("decision keywords here", day="YESTERDAY", topic="audio", limit=10)` 68 + - `search_journal("decision keywords here", day="$day_YYYYMMDD", topic="audio", limit=10)` 69 69 - Goal: Pinpoint exact time of decision (HH:MM:SS) 70 70 71 71 2. **Get full context:** 72 - - `get_resource("journal://transcripts/full/YESTERDAY/HHMMSS/30")` 72 + - `get_resource("journal://transcripts/full/$day_YYYYMMDD/HHMMSS/30")` 73 73 - Goal: Extract 30 minutes of raw activity around decision time 74 74 75 75 ### Step 2: Stakeholder & Dependency Mapping 76 76 77 77 1. **Identify all entities:** 78 - - `search_journal("entity names from decision", day="YESTERDAY")` 78 + - `search_journal("entity names from decision", day="$day_YYYYMMDD")` 79 79 - Goal: Find all people, teams, projects mentioned 80 80 81 81 2. **Map meeting participants:** 82 - - `get_events("YESTERDAY")` or `search_journal("[keywords]", day="YESTERDAY", topic="event")` 83 - - `search_journal("[keywords]", topic="news", facet="work", day="YESTERDAY")` for public announcements 82 + - `get_events("$day_YYYYMMDD")` or `search_journal("[keywords]", day="$day_YYYYMMDD", topic="event")` 83 + - `search_journal("[keywords]", topic="news", facet="work", day="$day_YYYYMMDD")` for public announcements 84 84 - Goal: Identify who needs to know about this decision 85 85 86 86 ### Step 3: Historical Precedent Mining (30-day lookback) ··· 96 96 ### Step 4: Forward Impact Assessment (2-6 hours post-decision) 97 97 98 98 1. **Check for communications:** 99 - - `search_journal("[keywords]", day="YESTERDAY", topic="audio", limit=10)` 100 - - `search_journal("[keywords]", topic="news", day="YESTERDAY")` for decision announcements 99 + - `search_journal("[keywords]", day="$day_YYYYMMDD", topic="audio", limit=10)` 100 + - `search_journal("[keywords]", topic="news", day="$day_YYYYMMDD")` for decision announcements 101 101 - Goal: Find follow-up notifications or discussions 102 102 103 103 2. **Review meetings:** 104 - - `search_journal("[keywords]", day="YESTERDAY", topic="event")` 104 + - `search_journal("[keywords]", day="$day_YYYYMMDD", topic="event")` 105 105 - Goal: See if decision was discussed 106 106 107 107 3. **Check messaging:** 108 - - `get_resource("journal://insight/YESTERDAY/messages")` 108 + - `get_resource("journal://insight/$day_YYYYMMDD/messages")` 109 109 - Goal: Verify notifications were sent 110 110 111 111 ### Step 5: Gap Detection 112 112 113 113 1. **Search for problems:** 114 - - `search_journal("rollback revert issue problem", day="YESTERDAY")` 114 + - `search_journal("rollback revert issue problem", day="$day_YYYYMMDD")` 115 115 - Goal: Identify emerging issues 116 116 117 117 2. **Verify updates:** 118 - - `search_journal("document update change commit", day="YESTERDAY", topic="audio")` 118 + - `search_journal("document update change commit", day="$day_YYYYMMDD", topic="audio")` 119 119 - Goal: Confirm tracking artifacts were updated 120 120 121 121 OBLIGATION CATEGORIES (derive expectations from decision type + precedents; adapt to what the data shows)
+17 -19
muse/joke_bot.md
··· 1 1 { 2 2 3 3 "title": "Joke Bot", 4 - "description": "Mines yesterday's journal for poignant moments and crafts a personalized joke delivered via message", 4 + "description": "Mines the analysis day's journal for poignant moments and crafts a personalized joke delivered via message", 5 5 "color": "#f9a825", 6 6 "schedule": "daily", 7 7 "priority": 99, 8 8 "tools": "default", 9 - "instructions": {"system": "journal", "facets": true} 9 + "instructions": {"system": "journal", "facets": true, "now": true, "day": true} 10 10 11 11 } 12 12 13 13 ### Executive Summary 14 - $Preferred has made a creative and subjective request: to analyze yesterday's journal data, find the most "poignant" and interesting material, and then leverage it to craft a hilarious joke to be sent as a message. This plan focuses on a comprehensive data-gathering operation for a single day to provide a rich set of raw material for the creative task. 14 + $Preferred has made a creative and subjective request: to analyze the analysis day's journal data, find the most "poignant" and interesting material, and then leverage it to craft a hilarious joke to be sent as a message. This plan focuses on a comprehensive data-gathering operation for a single day to provide a rich set of raw material for the creative task. 15 15 16 - The research will first build a complete picture of yesterday's activities, then dive into specific details to find moments of irony, frustration, or absurdity that can be used as comedic fodder. The final step is not to answer directly, but to use the `send_message` tool to deliver the crafted joke. 16 + The research will first build a complete picture of the analysis day's activities, then dive into specific details to find moments of irony, frustration, or absurdity that can be used as comedic fodder. The final step is not to answer directly, but to use the `send_message` tool to deliver the crafted joke. 17 17 18 18 - **Expected Outcome Type**: A single, creative message containing a joke. 19 19 - **Estimated Research Depth**: Comprehensive (for a single day). 20 20 21 21 ### Research Strategy 22 - The strategy is to conduct a three-phase data sweep of yesterday's journal entries. We will start broad to understand the day's main themes and then narrow our focus to find specific, quote-worthy, or event-specific details that have comedic potential. 22 + The strategy is to conduct a three-phase data sweep of the analysis day's journal entries. We will start broad to understand the day's main themes and then narrow our focus to find specific, quote-worthy, or event-specific details that have comedic potential. 23 23 24 - 1. **Broad Overview**: Use `search_journal` with day filter to get a complete list of all topics and structured activities from yesterday. This creates a high-level map of the day. 24 + 1. **Broad Overview**: Use `search_journal` with day filter to get a complete list of all topics and structured activities from the analysis day. This creates a high-level map of the day. 25 25 2. **Detailed Search**: Use `search_journal` with topic="audio" and keywords related to emotion, humor, and conflict (e.g., "frustrating", "ridiculous", "error", "lol") to pinpoint specific moments of interest. 26 26 3. **Contextual Analysis**: Use `get_resource` to pull the full context for the most promising findings from the previous phases. This raw material will be analyzed for comedic elements like irony, juxtaposition, or absurdity. 27 27 4. **Creative Synthesis & Delivery**: The final phase involves brainstorming joke concepts from the analyzed material, selecting the best one, and delivering it via `send_message`. 28 28 29 29 ### Detailed Research Steps 30 30 31 - *(Note: `{yesterday_YYYYMMDD}` should be replaced with the actual date for yesterday.)* 32 - 33 - **Phase 1: Discovery - Mapping Yesterday's Landscape** 31 + **Phase 1: Discovery - Mapping the Analysis Day's Landscape** 34 32 35 33 **Query syntax**: Searches match ALL words by default; use `OR` between words to match ANY (e.g., `apple OR orange`), quote phrases for exact matches (e.g., `"project meeting"`), and append `*` for prefix matching (e.g., `debug*`). 36 34 37 35 1. **Identify All Daily Topics**: 38 36 - **Tool**: `search_journal` 39 - - **Parameters**: `day={yesterday_YYYYMMDD}` (with an empty query string to retrieve all topics) 40 - - **Purpose**: To get a complete list of all themes and activities discussed or worked on yesterday. This provides the main "characters" and "settings" for potential jokes. 37 + - **Parameters**: `day=$day_YYYYMMDD` (with an empty query string to retrieve all topics) 38 + - **Purpose**: To get a complete list of all themes and activities discussed or worked on during the analysis day. This provides the main "characters" and "settings" for potential jokes. 41 39 - **Expected Outcomes**: A list of all topic insights from the day, which will help identify recurring themes or unusual combinations of activities. 42 40 43 41 2. **List All Structured Events**: 44 42 - **Tool**: `get_events` 45 - - **Parameters**: `day={yesterday_YYYYMMDD}` 43 + - **Parameters**: `day=$day_YYYYMMDD` 46 44 - **Purpose**: To identify all formal meetings, calls, or tasks. Corporate jargon, meeting mishaps, or task-related struggles are excellent sources of humor. 47 45 - **Expected Outcomes**: A timeline of the day's key events, such as "Project Phoenix Sync," "API Debugging Session," or "Team Standup." 48 46 49 47 3. **Find Emotionally Charged Moments**: 50 48 - **Tool**: `search_journal` 51 - - **Parameters**: `day={yesterday_YYYYMMDD}`, `topic="audio"`, `query="this is ridiculous" OR "I'm so confused" OR "why is this not working" OR "error" OR "hilarious" OR "lol"` 49 + - **Parameters**: `day=$day_YYYYMMDD`, `topic="audio"`, `query="this is ridiculous" OR "I'm so confused" OR "why is this not working" OR "error" OR "hilarious" OR "lol"` 52 50 - **Purpose**: To find specific quotes or screen interactions that indicate frustration, confusion, or amusement. These raw emotional moments are often the most poignant and funny. 53 51 - **Expected Outcomes**: A list of specific timestamps and text snippets that can be investigated further for their comedic context. 54 52 ··· 56 54 57 55 1. **Retrieve Full Context for Key Findings**: 58 56 - **Resources**: 59 - - `journal://insight/{yesterday_YYYYMMDD}/{topic}` for the 2-3 most prominent or ironically named topics discovered in Phase 1. 60 - - `journal://transcripts/full/{yesterday_YYYYMMDD}/{time}/{length}` for the most promising snippets found in the transcript search. Retrieve a 5-10 minute window around the snippet to understand the full conversation or activity. 57 + - `journal://insight/$day_YYYYMMDD/{topic}` for the 2-3 most prominent or ironically named topics discovered in Phase 1. 58 + - `journal://transcripts/full/$day_YYYYMMDD/{time}/{length}` for the most promising snippets found in the transcript search. Retrieve a 5-10 minute window around the snippet to understand the full conversation or activity. 61 59 - **Priority Order**: Prioritize transcript snippets first, as they contain direct quotes. Then, review insights for high-level irony. 62 60 - **Analysis Focus**: Read through the retrieved content, looking for: 63 61 - **Irony**: e.g., A meeting about "improving communication" where everyone was talking over each other. ··· 77 75 - **Purpose**: To deliver the final creative output as requested. 78 76 79 77 ### Query Optimization Strategy 80 - - **Primary Queries**: Broad, day-filtered searches to capture all topics and events from yesterday. 78 + - **Primary Queries**: Broad, day-filtered searches to capture all topics and events from the analysis day. 81 79 - **Alternative Queries**: For `search_journal` with topic="audio", if the initial emotional keywords yield no results, try searching for project codenames, specific colleagues' names, or technical terms that appeared frequently in the day's insights to find relevant conversations. 82 80 - **Refinement Approach**: This is a single-day analysis, so the strategy is to gather more data rather than refine. If initial searches are sparse, the fallback is to read through all topic insights from the day to find something poignant, even if not overtly "funny." 83 81 84 82 ### Potential Research Challenges 85 - - **A "Boring" Day**: If yesterday's activities were routine, the plan is to focus on the "poignant" aspect. A joke can be crafted from the mundane nature of the day itself. 83 + - **A "Boring" Day**: If the analysis day's activities were routine, the plan is to focus on the "poignant" aspect. A joke can be crafted from the mundane nature of the day itself. 86 84 - **Sensitive Information**: The journal may contain sensitive data. The creative process must transform the material into a joke that is self-deprecating or observational without revealing private details about projects or other people. 87 85 - **Subjectivity of Humor**: The final joke may not land perfectly. The goal of this plan is to provide the best possible source material to maximize the chance of success. 88 86 89 87 ### Success Criteria 90 - - **Completeness Indicators**: A full list of topics, events, and a set of interesting transcript snippets from yesterday have been collected and analyzed. 88 + - **Completeness Indicators**: A full list of topics, events, and a set of interesting transcript snippets from the analysis day have been collected and analyzed. 91 89 - **Quality Checkpoints**: The analysis has identified at least one moment of irony, absurdity, or relatable human struggle. 92 - - **Coverage Verification**: The final action is using `send_message` to deliver a joke that is clearly derived from the events of the specified day. 90 + - **Coverage Verification**: The final action is using `send_message` to deliver a joke that is clearly derived from the events of the analysis day.
+28 -10
tests/test_muse.py
··· 160 160 monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 161 161 162 162 result = compose_instructions( 163 - include_datetime=False, 164 - config_overrides={"facets": False}, 163 + config_overrides={"facets": False, "now": False, "day": False}, 165 164 ) 166 165 167 166 # With no datetime and no facets, extra_context should be empty/None 168 167 assert result["extra_context"] is None or result["extra_context"] == "" 169 168 170 - def test_include_datetime_false_excludes_time(self, monkeypatch, tmp_path): 171 - """Test that include_datetime=False excludes time from context.""" 169 + def test_now_false_excludes_time(self, monkeypatch, tmp_path): 170 + """Test that now=False excludes current datetime from context.""" 172 171 think_dir = tmp_path / "think" 173 172 think_dir.mkdir() 174 173 journal_txt = think_dir / "journal.md" ··· 180 179 monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 181 180 182 181 result = compose_instructions( 183 - include_datetime=False, 184 - config_overrides={"facets": False}, 182 + config_overrides={"facets": False, "now": False}, 185 183 ) 186 184 187 185 extra = result.get("extra_context") or "" 188 186 assert "Current Date and Time" not in extra 189 187 190 - def test_include_datetime_true_includes_time(self, monkeypatch, tmp_path): 191 - """Test that include_datetime=True includes time in context.""" 188 + def test_now_true_includes_time(self, monkeypatch, tmp_path): 189 + """Test that now=True includes current datetime in context.""" 192 190 think_dir = tmp_path / "think" 193 191 think_dir.mkdir() 194 192 journal_txt = think_dir / "journal.md" ··· 200 198 monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 201 199 202 200 result = compose_instructions( 203 - include_datetime=True, 204 - config_overrides={"facets": False}, 201 + config_overrides={"facets": False, "now": True}, 205 202 ) 206 203 207 204 assert "Current Date and Time" in result["extra_context"] 205 + 206 + def test_day_true_includes_analysis_day(self, monkeypatch, tmp_path): 207 + """Test that day=True includes analysis day in context.""" 208 + think_dir = tmp_path / "think" 209 + think_dir.mkdir() 210 + journal_txt = think_dir / "journal.md" 211 + journal_txt.write_text("System instruction") 212 + 213 + import think.prompts 214 + 215 + monkeypatch.setattr(think.prompts, "__file__", str(think_dir / "prompts.py")) 216 + monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 217 + 218 + result = compose_instructions( 219 + analysis_day="20250115", 220 + config_overrides={"facets": False, "day": True}, 221 + ) 222 + 223 + extra = result.get("extra_context") or "" 224 + assert "Analysis Day" in extra 225 + assert "20250115" in extra 208 226 209 227 def test_sources_returned_from_defaults(self, monkeypatch, tmp_path): 210 228 """Test that sources config is returned with defaults (all false)."""
+13 -11
think/agents.py
··· 225 225 span: List of segment keys 226 226 227 227 Returns: 228 - Dict with day, date, segment_start, segment_end as available 228 + Dict with template variables: 229 + - day: Friendly format (e.g., "Sunday, February 2, 2025") 230 + - day_YYYYMMDD: Raw day string (e.g., "20250202") 231 + - segment_start, segment_end: Time strings if segment/span provided 229 232 """ 230 233 context: dict[str, str] = {} 231 234 if not day: 232 235 return context 233 236 234 - context["day"] = day 235 - context["date"] = format_day(day) 237 + context["day"] = format_day(day) 238 + context["day_YYYYMMDD"] = day 236 239 237 240 if segment: 238 241 start_str, end_str = format_segment_times(segment) ··· 322 325 - tools: Expanded tool list (if tool agent) 323 326 - system_instruction: System prompt 324 327 - user_instruction: Agent instruction from .md file 325 - - extra_context: Facets, datetime context 328 + - extra_context: Facets and context from instructions.now/day settings 326 329 - prompt: User's runtime query/request 327 330 - transcript: Clustered transcript (if day provided) 328 331 - output_path: Where to write output (if output format set) 329 332 - skip_reason: Why to skip (if applicable) 330 333 331 - Day vs Non-Day Processing: 332 - - Non-day (interactive): Includes current datetime in context, no transcript 333 - - Day-based (historical): Excludes datetime, loads clustered transcript 334 + Context is controlled by explicit frontmatter settings: 335 + - instructions.now: Include current datetime in extra_context 336 + - instructions.day: Include analysis day context (requires day parameter) 337 + - Day-based calls also load clustered transcript 334 338 335 339 Args: 336 340 request: Raw request dict from cortex ··· 350 354 output_path_override = request.get("output_path") 351 355 user_prompt = request.get("prompt", "") 352 356 353 - # Load complete agent config with appropriate datetime context: 354 - # - Interactive (no day): include_datetime=True for current context 355 - # - Day-based analysis: include_datetime=False for historical data 356 - config = get_agent(name, facet=facet, include_datetime=not day) 357 + # Load complete agent config, passing day for instructions.day context 358 + config = get_agent(name, facet=facet, analysis_day=day) 357 359 358 360 # Config now contains all frontmatter fields plus: 359 361 # - path: Path to the .md file
+34 -26
think/muse.py
··· 308 308 _DEFAULT_INSTRUCTIONS = { 309 309 "system": None, 310 310 "facets": False, 311 + "now": False, 312 + "day": False, 311 313 "sources": { 312 314 "audio": False, 313 315 "screen": False, ··· 339 341 result = defaults.copy() 340 342 341 343 # Merge top-level keys 342 - for key in ("system", "facets"): 344 + for key in ("system", "facets", "now", "day"): 343 345 if key in overrides: 344 346 result[key] = overrides[key] 345 347 ··· 355 357 user_prompt: str | None = None, 356 358 user_prompt_dir: Path | None = None, 357 359 facet: str | None = None, 358 - include_datetime: bool = True, 360 + analysis_day: str | None = None, 359 361 config_overrides: dict | None = None, 360 362 ) -> dict: 361 363 """Compose instruction components for agents or generators. ··· 374 376 facet: 375 377 Optional facet name to focus on. When provided, extra_context includes 376 378 only this facet's info (detail level controlled by "facets" setting). 377 - include_datetime: 378 - Whether to include current date/time in extra_context. Default True 379 - for agents (real-time chat), typically False for generators (past analysis). 379 + analysis_day: 380 + Optional day in YYYYMMDD format for day-based analysis. Used when 381 + instructions.day is true to include analysis day context. 380 382 config_overrides: 381 383 Optional dict from .json "instructions" key. Supported keys: 382 - - "system": prompt name for system instruction (default: "journal") 383 - - "facets": false | true | "full" (default: true) 384 + - "system": prompt name for system instruction (default: None) 385 + - "facets": false | true | "full" (default: false) 384 386 false = skip facet context 385 387 true = include facet context with names only 386 388 "full" = include facet context with full descriptions 387 389 For faceted generators, shows focused facet; for unfaceted, shows all facets. 390 + - "now": false | true (default: false) 391 + true = include current date/time in extra_context 392 + - "day": false | true (default: false) 393 + true = include analysis day context (requires analysis_day parameter) 388 394 - "sources": {"audio": bool, "screen": bool, "agents": bool|dict} 389 395 The "agents" source can be: 390 396 - bool: True (all agents), False (no agents) ··· 398 404 - system_instruction: str - loaded from "system" prompt 399 405 - system_prompt_name: str - name of system prompt (for cache keys) 400 406 - user_instruction: str | None - loaded from user_prompt if provided 401 - - extra_context: str | None - facets + datetime 407 + - extra_context: str | None - facets + now + day context 402 408 - sources: dict - {"audio": bool, "screen": bool, "agents": bool|dict} 403 409 """ 404 - from datetime import datetime 410 + from think.utils import format_day 405 411 406 412 # Merge defaults with overrides 407 413 cfg = _merge_instructions_config(_DEFAULT_INSTRUCTIONS, config_overrides) ··· 426 432 else: 427 433 result["user_instruction"] = None 428 434 429 - # Build extra_context based on facets setting 430 - # Values: false (skip), true (names only), "full" (with descriptions) 435 + # Build extra_context based on settings 431 436 extra_parts = [] 437 + 438 + # Facets context 432 439 facets_setting = cfg.get("facets", False) 433 440 facets_full = facets_setting == "full" 434 441 ··· 453 460 except Exception: 454 461 pass # Ignore if facets can't be loaded 455 462 456 - # Add current date/time if requested 457 - if include_datetime: 458 - now = datetime.now() 459 - try: 460 - import tzlocal 463 + # Current date/time context (instructions.now) 464 + if cfg.get("now"): 465 + from think.prompts import format_current_datetime 461 466 462 - local_tz = tzlocal.get_localzone() 463 - now_local = now.astimezone(local_tz) 464 - time_str = now_local.strftime("%A, %B %d, %Y at %I:%M %p %Z") 465 - except Exception: 466 - time_str = now.strftime("%A, %B %d, %Y at %I:%M %p") 467 + time_str = format_current_datetime() 467 468 extra_parts.append(f"## Current Date and Time\nToday is {time_str}") 469 + 470 + # Analysis day context (instructions.day) 471 + if cfg.get("day") and analysis_day: 472 + day_friendly = format_day(analysis_day) 473 + extra_parts.append( 474 + f"## Analysis Day\nYou are analyzing data from {day_friendly} ({analysis_day})." 475 + ) 468 476 469 477 result["extra_context"] = "\n\n".join(extra_parts).strip() if extra_parts else None 470 478 ··· 555 563 def get_agent( 556 564 name: str = "default", 557 565 facet: str | None = None, 558 - include_datetime: bool = True, 566 + analysis_day: str | None = None, 559 567 ) -> dict: 560 568 """Return complete agent configuration by name. 561 569 ··· 571 579 Optional facet name to focus on. When provided, includes detailed 572 580 information for just this facet (with full entity details) instead 573 581 of summaries of all facets. 574 - include_datetime: 575 - Whether to include current datetime in extra_context. Default True 576 - for interactive agents, set False for historical analysis (day-based). 582 + analysis_day: 583 + Optional day in YYYYMMDD format. When provided and instructions.day is 584 + true, includes analysis day context in extra_context. 577 585 578 586 Returns 579 587 ------- ··· 608 616 user_prompt=agent_name, 609 617 user_prompt_dir=agent_dir, 610 618 facet=facet, 611 - include_datetime=include_datetime, 619 + analysis_day=analysis_day, 612 620 config_overrides=instructions_config, 613 621 ) 614 622
+29 -4
think/prompts.py
··· 72 72 """Load and substitute template files from think/templates/ directory. 73 73 74 74 Raw templates are cached, but substitution is performed on each call 75 - to support context-dependent variables like $date and $segment_start. 75 + to support context-dependent variables like $day and $segment_start. 76 76 77 77 Parameters 78 78 ---------- 79 79 template_vars: 80 80 Optional variables to substitute into templates. Templates can use 81 - identity vars ($name, $preferred), context vars ($day, $date, 82 - $segment_start, $segment_end), and other template vars. 81 + identity vars ($name, $preferred), context vars ($day, $day_YYYYMMDD, 82 + $segment_start, $segment_end, $now), and other template vars. 83 83 84 84 Returns 85 85 ------- ··· 104 104 return substituted 105 105 106 106 107 + def format_current_datetime() -> str: 108 + """Format current datetime with timezone for display. 109 + 110 + Returns a human-readable string like "Monday, February 3, 2025 at 10:30 AM PST". 111 + Falls back to timezone-naive format if tzlocal is unavailable. 112 + 113 + This is the single source of truth for $now template variable and 114 + instructions.now context formatting. 115 + """ 116 + from datetime import datetime 117 + 118 + now = datetime.now() 119 + try: 120 + import tzlocal 121 + 122 + local_tz = tzlocal.get_localzone() 123 + now_local = now.astimezone(local_tz) 124 + return now_local.strftime("%A, %B %d, %Y at %I:%M %p %Z") 125 + except Exception: 126 + return now.strftime("%A, %B %d, %Y at %I:%M %p") 127 + 128 + 107 129 # --------------------------------------------------------------------------- 108 130 # Prompt Loading 109 131 # --------------------------------------------------------------------------- ··· 182 204 - Each file becomes a variable named after its stem 183 205 - Example: daily_preamble.md -> $daily_preamble 184 206 - Templates are pre-processed with identity and context vars, so templates 185 - can use $date, $preferred, etc. before being substituted into prompts 207 + can use $day, $preferred, $now, etc. before being substituted into prompts 186 208 187 209 Callers can provide additional context variables via the ``context`` parameter. 188 210 Context variables override identity and template variables if there's a collision. ··· 238 260 config = get_config() 239 261 identity = config.get("identity", {}) 240 262 template_vars = _flatten_identity_to_template_vars(identity) 263 + 264 + # Add $now template variable (current datetime) 265 + template_vars["now"] = format_current_datetime() 241 266 242 267 # Merge caller-provided context (overrides identity vars if collision) 243 268 if context: