personal memory agent
0
fork

Configure Feed

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

Implement import onboarding integration (Scope C)

Wire import offers into both onboarding paths, add import-aware triage,
import awareness tracking, and chat bar placeholder changes.

- think/awareness.py: add imports section with get_imports(),
record_import(), record_import_offer_declined(), record_import_nudge()
- apps/awareness/call.py: add `sol call awareness imports` CLI command
- think/importers/cli.py: call record_import() on successful completion
- muse/onboarding.md: extend Path B with import offer before completion
- muse/observation_review.md: extend Path A with import offer (new step 5)
- muse/triage.md: add import awareness section for soft nudges
- convey/triage.py: inject import state into triage routing context
- convey/apps.py: import-aware placeholder for new users (day_count < 3)

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

+243 -4
+40
apps/awareness/call.py
··· 81 81 typer.echo(json.dumps(state, indent=2)) 82 82 83 83 84 + @app.command("imports") 85 + def imports_cmd( 86 + record: str | None = typer.Option( 87 + None, "--record", "-r", help="Record a completed import (source type)." 88 + ), 89 + declined: bool = typer.Option( 90 + False, "--declined", help="Record that user declined import offer." 91 + ), 92 + nudge: bool = typer.Option( 93 + False, "--nudge", help="Record that triage nudged about imports." 94 + ), 95 + ) -> None: 96 + """Read or update import tracking state.""" 97 + from think.awareness import ( 98 + get_imports, 99 + record_import, 100 + record_import_nudge, 101 + record_import_offer_declined, 102 + ) 103 + 104 + if record: 105 + state = record_import(record) 106 + typer.echo(json.dumps(state, indent=2)) 107 + return 108 + 109 + if declined: 110 + state = record_import_offer_declined() 111 + typer.echo(json.dumps(state, indent=2)) 112 + return 113 + 114 + if nudge: 115 + state = record_import_nudge() 116 + typer.echo(json.dumps(state, indent=2)) 117 + return 118 + 119 + # No flags — read current state 120 + state = get_imports() 121 + typer.echo(json.dumps(state, indent=2)) 122 + 123 + 84 124 @app.command("log-read") 85 125 def log_read_cmd( 86 126 day: str | None = typer.Argument(
+3
convey/apps.py
··· 79 79 if onboarding_status == "interviewing": 80 80 return "Tell me about your work..." 81 81 if onboarding_status in ("complete", "skipped"): 82 + imports = awareness_current.get("imports", {}) 83 + if not imports.get("has_imported") and day_count < 3: 84 + return "Bring in past conversations, calendar, or notes to give me context..." 82 85 if awareness_current.get("journal", {}).get("first_daily_ready"): 83 86 if day_count < 2: 84 87 return "Your first daily analysis is ready — ask me what I found..."
+26
convey/triage.py
··· 77 77 "to open a chat with the recommendation agent if they want to proceed." 78 78 ) 79 79 elif onboarding_status in ("complete", "skipped"): 80 + # Add import awareness context 81 + try: 82 + from think.awareness import get_imports 83 + 84 + imports = get_imports() 85 + if not imports.get("has_imported"): 86 + offer_declined = imports.get("offer_declined") 87 + last_nudge = imports.get("last_nudge") 88 + context_lines.append( 89 + f"Import state: no imports yet. " 90 + f"offer_declined={offer_declined}, last_nudge={last_nudge}. " 91 + "If contextually appropriate and no recent nudge, " 92 + "you may suggest importing once (then record with " 93 + "`sol call awareness imports --nudge`)." 94 + ) 95 + else: 96 + count = imports.get("import_count", 0) 97 + sources = imports.get("sources_used", []) 98 + context_lines.append( 99 + f"Import state: {count} import(s) from {', '.join(sources)}. " 100 + "User has imported — no nudging needed. " 101 + "If they just returned from an import, offer another source." 102 + ) 103 + except Exception: 104 + pass # Don't let import context break triage 105 + 80 106 # Add daily agent output context for post-onboarding users 81 107 try: 82 108 from datetime import datetime, timedelta
+23 -2
muse/observation_review.md
··· 74 74 75 75 Entity types: Person, Company, Project, Tool 76 76 77 - ## Step 5: Complete Onboarding 77 + ## Step 5: Offer Imports 78 + 79 + After creating facets and attaching entities, **before** completing onboarding, offer to import existing data: 80 + 81 + > Nice — I've set up [facets] based on what I observed. Your journal now has structure, but it's mostly today's data. 82 + > 83 + > Want to fill in the backstory? If you have ChatGPT conversations, calendar exports, notes, or Kindle highlights, I can import them so I can see patterns going back months or years. 84 + > 85 + > What do you use that we could bring in? 86 + 87 + **If user picks a source:** 88 + 1. Read the export guide from `apps/import/guides/{source}.md` (map: Calendar→ics, ChatGPT→chatgpt, Claude→claude, Gemini→gemini, Notes→obsidian, Kindle→kindle) 89 + 2. Present the export instructions conversationally 90 + 3. Run `sol call chat redirect "Import my {source}" --app import --path "/app/import/source/{source}"` to hand off to the import app 91 + 4. After redirecting, tell the user you'll take them to the import page to upload the file 78 92 79 - After reviewing all suggestions: 93 + **If user says "skip" or "not now":** 94 + 1. Run `sol call awareness imports --declined` to record the decline 95 + 2. Say: "No problem — you can import anytime from the Import app. I'll remind you once you've settled in." 96 + 3. Proceed to complete onboarding 97 + 98 + ## Step 6: Complete Onboarding 99 + 100 + After the import offer (whether they chose a source or skipped): 80 101 81 102 ```bash 82 103 sol call awareness onboarding --complete
+30 -2
muse/onboarding.md
··· 73 73 - Do not create facets or entities without user confirmation. 74 74 - After setup, mark onboarding complete with `sol call awareness onboarding --complete`, then summarize what was created and tell the user they can continue with the regular assistant. 75 75 76 + ### Import Offer 77 + 78 + After creating facets and attaching entities, **before** running `sol call awareness onboarding --complete`, offer to import existing data: 79 + 80 + > Your journal is set up with [facets] and I've noted the people and projects you mentioned. 81 + > 82 + > Want to bring in some history? I can help you import data from tools you've already been using — it'll give me years of context about your life instead of starting from scratch. 83 + > 84 + > I can help with: 85 + > - 📅 **Calendar** — Google Calendar, Apple Calendar, Outlook 86 + > - 🤖 **AI conversations** — ChatGPT, Claude, or Gemini 87 + > - 📝 **Notes** — Obsidian vault or Logseq graph 88 + > - 📚 **Kindle highlights** — books and clippings 89 + > 90 + > Which sounds useful, or would you rather skip for now? 91 + 92 + **If user picks a source:** 93 + 1. Read the export guide from `apps/import/guides/{source}.md` (map: Calendar→ics, ChatGPT→chatgpt, Claude→claude, Gemini→gemini, Notes→obsidian, Kindle→kindle) 94 + 2. Present the export instructions conversationally 95 + 3. Run `sol call chat redirect "Import my {source}" --app import --path "/app/import/source/{source}"` to hand off to the import app 96 + 4. After redirecting, tell the user you'll take them to the import page to upload the file 97 + 98 + **If user says "skip" or "not now":** 99 + 1. Run `sol call awareness imports --declined` to record the decline 100 + 2. Say: "No problem — you can import anytime from the Import app. I'll remind you once you've settled in." 101 + 3. Proceed to complete onboarding normally 102 + 76 103 Example onboarding flow: 77 104 78 105 1. Ask for life contexts. ··· 80 107 3. Confirm created facets with `sol call journal facets`. 81 108 4. Ask what entities belong in each facet. 82 109 5. Attach each via `sol call entities attach`. 83 - 6. Run `sol call awareness onboarding --complete`. 84 - 7. Summarize what was created — name the specific facets and entities you just set up. Then suggest a concrete first thing to try: pick one of the entities you just attached and say something like "Try asking me 'tell me about [entity name]' to see how I can help." Keep it warm and grounded in what was just created together. 110 + 6. Offer imports (see Import Offer above). 111 + 7. Run `sol call awareness onboarding --complete`. 112 + 8. Summarize what was created — name the specific facets and entities you just set up. Then suggest a concrete first thing to try: pick one of the entities you just attached and say something like "Try asking me 'tell me about [entity name]' to see how I can help." Keep it warm and grounded in what was just created together.
+19
muse/triage.md
··· 70 70 - **Status "observing"**: If the user asks "what have you noticed?", "how's it going?", "what are you learning?", or similar — read recent observations with `sol call awareness log-read --kind observation --limit 5` and summarize what the system has seen so far. Be encouraging about the observation progress. 71 71 72 72 - **Status "ready"**: Recommendations are available! Proactively suggest reviewing them: "I've finished observing and have suggestions for organizing your journal. Want to take a look?" If the user agrees, redirect to the observation review agent: `sol call chat redirect "Review my observation suggestions" --muse observation_review` 73 + 74 + ## Import Awareness 75 + 76 + When onboarding is complete, check import state with `sol call awareness imports`: 77 + 78 + - **After an import completes** (user returns to chat): The import system updates awareness automatically. If you see `has_imported: true` and new sources in `sources_used`, offer to import from another source: "I just processed your [source] import. Want to import from another source, or explore what I found?" 79 + 80 + - **Soft import nudge**: If all of these are true, you may weave a single soft import mention into your response: 81 + 1. Onboarding is complete (`sol call awareness onboarding` → status: complete) 82 + 2. No imports done (`has_imported: false`) 83 + 3. Import offer not recently declined (no `offer_declined` or >3 days ago) 84 + 4. No recent nudge (`last_nudge` is null) 85 + 5. The user's message touches on their journal, data, or what solstone can do 86 + 87 + After mentioning imports, run `sol call awareness imports --nudge` to record it. Do **not** repeat this nudge. 88 + 89 + - **Available sources**: Calendar (ics), ChatGPT (chatgpt), Claude (claude), Gemini (gemini), Notes (obsidian), Kindle (kindle) 90 + 91 + - If the user wants to import, read the guide from `apps/import/guides/{source}.md`, present it, then redirect: `sol call chat redirect "Import my {source}" --app import --path "/app/import/source/{source}"`
+94
think/awareness.py
··· 234 234 ) 235 235 append_log("state", key="onboarding.complete") 236 236 return state 237 + 238 + 239 + # --- Import tracking convenience functions --- 240 + 241 + 242 + def _ensure_imports_section() -> dict[str, Any]: 243 + """Ensure the imports section exists in current state, return it.""" 244 + state = get_current() 245 + if "imports" not in state: 246 + state["imports"] = { 247 + "has_imported": False, 248 + "import_count": 0, 249 + "sources_used": [], 250 + "offer_declined": None, 251 + "last_nudge": None, 252 + } 253 + _write_current(state) 254 + return state["imports"] 255 + 256 + 257 + def get_imports() -> dict[str, Any]: 258 + """Return the current import tracking state, or defaults if none.""" 259 + state = get_current() 260 + return state.get("imports", { 261 + "has_imported": False, 262 + "import_count": 0, 263 + "sources_used": [], 264 + "offer_declined": None, 265 + "last_nudge": None, 266 + }) 267 + 268 + 269 + def record_import(source_type: str) -> dict[str, Any]: 270 + """Record a completed import. 271 + 272 + Parameters 273 + ---------- 274 + source_type : str 275 + Import source type (e.g., "chatgpt", "ics", "claude") 276 + 277 + Returns 278 + ------- 279 + dict 280 + The updated imports state 281 + """ 282 + _ensure_imports_section() 283 + imports = get_imports() 284 + sources = imports.get("sources_used", []) 285 + if source_type not in sources: 286 + sources.append(source_type) 287 + state = update_state( 288 + "imports", 289 + { 290 + "has_imported": True, 291 + "import_count": imports.get("import_count", 0) + 1, 292 + "sources_used": sources, 293 + }, 294 + ) 295 + append_log("state", key="imports.completed", data={"source_type": source_type}) 296 + return state 297 + 298 + 299 + def record_import_offer_declined() -> dict[str, Any]: 300 + """Record that the user declined an import offer. 301 + 302 + Returns 303 + ------- 304 + dict 305 + The updated imports state 306 + """ 307 + _ensure_imports_section() 308 + state = update_state( 309 + "imports", 310 + {"offer_declined": _now_iso()}, 311 + ) 312 + append_log("state", key="imports.offer_declined") 313 + return state 314 + 315 + 316 + def record_import_nudge() -> dict[str, Any]: 317 + """Record that triage nudged the user about imports. 318 + 319 + Returns 320 + ------- 321 + dict 322 + The updated imports state 323 + """ 324 + _ensure_imports_section() 325 + state = update_state( 326 + "imports", 327 + {"last_nudge": _now_iso()}, 328 + ) 329 + append_log("state", key="imports.nudge_sent") 330 + return state
+8
think/importers/cli.py
··· 1025 1025 except Exception as e: 1026 1026 logger.warning(f"Failed to update import metadata: {e}") 1027 1027 1028 + # Update awareness import tracking 1029 + try: 1030 + from think.awareness import record_import 1031 + 1032 + record_import(processing_results.get("source_type", "generic")) 1033 + except Exception as e: 1034 + logger.warning(f"Failed to update import awareness: {e}") 1035 + 1028 1036 # Emit completed event 1029 1037 duration_ms = int((time.monotonic() - _start_time) * 1000) 1030 1038 output_files_relative = [_get_relative_path(f) for f in all_created_files]