personal memory agent
0
fork

Configure Feed

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

chore: remove legacy onboarding dead code

Delete disabled onboarding/observation/firstday_checkin talent files,
tests, CLI commands, and all stale references. Set first_daily_ready
unconditionally on first daily analysis (was gated on onboarding
completion status that no longer exists). Rename test_onboarding.py
to test_convey_apps.py retaining only the live convey app tests.

9 files deleted, ~3700 lines removed across 35 files.

+81 -3343
+1 -1
AGENTS.md
··· 28 28 29 29 - **`sol/self.md`** — Your identity file. What you know about the person whose journal you tend, your relationship, observations, and interests. Update when something genuinely changes your understanding. 30 30 - **`sol/agency.md`** — Your initiative queue. Issues you've found, curation opportunities, follow-throughs. Update when you notice something worth tracking. 31 - - **`sol/partner.md`** — Your understanding of the owner's behavioral patterns. Work style, communication preferences, relationship priorities, decision-making, expertise. Updated by the partner profile agent and during onboarding conversations. 31 + - **`sol/partner.md`** — Your understanding of the owner's behavioral patterns. Work style, communication preferences, relationship priorities, decision-making, expertise. Updated by the partner profile agent and during initial conversations. 32 32 33 33 ### How to write 34 34
+1 -45
apps/awareness/call.py
··· 16 16 @app.command("status") 17 17 def status( 18 18 section: str | None = typer.Argument( 19 - None, help="Section to read (e.g., 'onboarding'). Omit for all." 19 + None, help="Section to read (e.g., 'journal'). Omit for all." 20 20 ), 21 21 ) -> None: 22 22 """Show current awareness state.""" ··· 35 35 typer.echo(json.dumps(value, indent=2)) 36 36 else: 37 37 typer.echo(json.dumps(state, indent=2)) 38 - 39 - 40 - @app.command("onboarding") 41 - def onboarding_cmd( 42 - path: str | None = typer.Option( 43 - None, "--path", "-p", help="Onboarding path: 'a' (observe) or 'b' (interview)." 44 - ), 45 - skip: bool = typer.Option(False, "--skip", help="Skip onboarding."), 46 - complete: bool = typer.Option( 47 - False, "--complete", help="Mark onboarding complete." 48 - ), 49 - ) -> None: 50 - """Read or update onboarding state.""" 51 - from think.awareness import ( 52 - complete_onboarding, 53 - get_onboarding, 54 - skip_onboarding, 55 - start_onboarding, 56 - ) 57 - 58 - if skip: 59 - state = skip_onboarding() 60 - typer.echo(json.dumps(state, indent=2)) 61 - return 62 - 63 - if complete: 64 - state = complete_onboarding() 65 - typer.echo(json.dumps(state, indent=2)) 66 - return 67 - 68 - if path: 69 - if path not in ("a", "b"): 70 - typer.echo("Error: --path must be 'a' or 'b'", err=True) 71 - raise typer.Exit(1) 72 - state = start_onboarding(path) 73 - typer.echo(json.dumps(state, indent=2)) 74 - return 75 - 76 - # No flags — read current state 77 - state = get_onboarding() 78 - if not state: 79 - typer.echo("No onboarding state yet.") 80 - return 81 - typer.echo(json.dumps(state, indent=2)) 82 38 83 39 84 40 @app.command("imports")
+1 -1
apps/home/events.py
··· 15 15 16 16 logger = logging.getLogger(__name__) 17 17 18 - TRIAGE_AGENT_NAMES = {"unified", "triage", "onboarding"} 18 + TRIAGE_AGENT_NAMES = {"unified", "triage"} 19 19 20 20 21 21 @on_event("cortex", "finish")
+1 -1
convey/root.py
··· 125 125 126 126 @bp.route("/") 127 127 def index() -> Any: 128 - """Root redirect — always to home, onboarding talent handles new journals.""" 128 + """Root redirect — always to home; the app handles new journals there.""" 129 129 return redirect(url_for("app:home.index"))
+2 -2
docs/SOLCLI.md
··· 312 312 | `transcripts` | `apps/transcripts/call.py` | list, read, segments | 313 313 | `support` | `apps/support/call.py` | register, search, article, create, list, show, reply, attach, feedback, announcements, diagnose | 314 314 | `agent` | `apps/agent/call.py` | name, set-name, reset, thickness, set-owner, sol-init | 315 - | `awareness` | `apps/awareness/call.py` | status, onboarding, imports, log, log-read | 315 + | `awareness` | `apps/awareness/call.py` | status, imports, log, log-read | 316 316 | `journal` | `think/tools/call.py` | search, events, facets, facet (show/create/update/rename/mute/unmute/delete/merge), news, agents, read, imports, import, retention purge, storage-summary | 317 317 | `routines` | `think/tools/routines.py` | list, templates, create, edit, delete, run, output, suggestions, suggest-respond, suggest-state | 318 318 | `identity` | `think/tools/sol.py` | self, partner, agency, pulse, briefing | ··· 327 327 - Core skills: `talent/<name>/SKILL.md` 328 328 329 329 **Skill ≠ call command.** Not every skill has a corresponding `call.py`, and not every `call.py` has a skill: 330 - - `health`, `coding`, `vit`, `onboarding` have skills but no `call.py` 330 + - `health`, `coding`, `vit` have skills but no `call.py` 331 331 - Some call apps provide the CLI while the skill provides agent behavioral context 332 332 333 333 Skills document the CLI commands but also add behavioral guidance beyond what `--help` shows (e.g., "check upcoming before adding a future todo to avoid duplicates").
+1 -1
sol/identity.md
··· 26 26 27 27 - **`sol/self.md`** — Your identity file. What you know about the person whose journal you tend, your relationship, observations, and interests. Update when something genuinely changes your understanding. 28 28 - **`sol/agency.md`** — Your initiative queue. Issues you've found, curation opportunities, follow-throughs. Update when you notice something worth tracking. 29 - - **`sol/partner.md`** — Your understanding of the owner's behavioral patterns. Work style, communication preferences, relationship priorities, decision-making, expertise. Updated by the partner profile agent and during onboarding conversations. 29 + - **`sol/partner.md`** — Your understanding of the owner's behavioral patterns. Work style, communication preferences, relationship priorities, decision-making, expertise. Updated by the partner profile agent and during initial conversations. 30 30 31 31 ### How to write 32 32
+1 -1
talent/awareness_tender.md
··· 19 19 20 20 Read current state using these tools: 21 21 22 - 1. `sol call awareness status` — capture, processing, and onboarding state 22 + 1. `sol call awareness status` — capture, processing, import, and journal state 23 23 2. `sol call identity self` — identity summary (skim for key changes) 24 24 3. `sol call calendar list` — today's events 25 25 4. `sol call routines list` — active routines and recent outputs
+1 -1
talent/chat.md
··· 56 56 | todos | Adding, completing, canceling, or listing todos and action items | 57 57 | speakers | Speaker identification, voice recognition, managing the speaker library | 58 58 | support | Bug reports, help requests, filing tickets, feedback, KB search, diagnostics | 59 - | awareness | Checking onboarding, observation, or system state | 59 + | awareness | Checking system state | 60 60 61 61 ## Speaker Intelligence 62 62
-18
talent/firstday_checkin.md
··· 1 - { 2 - "type": "generate", 3 - "title": "First-Day Check-In", 4 - "description": "One-shot check-in after onboarding — spawns support agent chat", 5 - "disabled": true, 6 - "schedule": "segment", 7 - "priority": 98, 8 - "output": "text", 9 - "hook": {"pre": "firstday_checkin", "post": "firstday_checkin"}, 10 - "tier": 3, 11 - "thinking_budget": 512, 12 - "max_output_tokens": 256, 13 - "exclude_streams": ["import.*"] 14 - } 15 - 16 - This generator exists only to trigger the first-day check-in via its pre/post hooks. The pre-hook handles all logic — if it doesn't skip, the post-hook spawns a support agent chat. The LLM output is unused. 17 - 18 - Output "ok" — nothing else needed.
-115
talent/firstday_checkin.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """First-day check-in hooks. 5 - 6 - Pre-hook: guards on awareness state — skips immediately (zero API cost) 7 - unless onboarding is complete and ~1 hour has elapsed since completion. 8 - One-shot: once the check-in fires, it never fires again. 9 - 10 - Post-hook: records that the check-in was sent and spawns a support 11 - agent chat for the user. 12 - """ 13 - 14 - from __future__ import annotations 15 - 16 - import logging 17 - from datetime import datetime 18 - 19 - logger = logging.getLogger(__name__) 20 - 21 - # Minimum hours after onboarding completion before check-in fires 22 - MIN_HOURS_AFTER_COMPLETE = 1.0 23 - 24 - 25 - def pre_process(context: dict) -> dict | None: 26 - """Guard: skip unless onboarding is complete and 1+ hour has elapsed. 27 - 28 - Returns dict with skip_reason to skip, or None to proceed. 29 - """ 30 - from think.awareness import get_onboarding 31 - 32 - onboarding = get_onboarding() 33 - status = onboarding.get("status") 34 - 35 - # Only fire after onboarding completes 36 - if status != "complete": 37 - return {"skip_reason": "not_complete"} 38 - 39 - # Only fire once 40 - if onboarding.get("firstday_checkin_sent"): 41 - return {"skip_reason": "already_sent"} 42 - 43 - # Check elapsed time since onboarding started 44 - # (onboarding.started is when they began, but we want time since completion; 45 - # since complete_onboarding() doesn't record a timestamp, use started + a 46 - # generous window — 1 hour after they started is a reasonable proxy for 47 - # "settled in") 48 - started = onboarding.get("started", "") 49 - if not started: 50 - return {"skip_reason": "no_start_time"} 51 - 52 - hours = _elapsed_hours(started) 53 - if hours < MIN_HOURS_AFTER_COMPLETE: 54 - return {"skip_reason": "too_soon"} 55 - 56 - # All conditions met — proceed 57 - return None 58 - 59 - 60 - def post_process(result: str, context: dict) -> str | None: 61 - """Record check-in and spawn support agent chat.""" 62 - from think.awareness import append_log, update_state 63 - 64 - # Record that we sent it (prevents repeat) 65 - update_state("onboarding", {"firstday_checkin_sent": _now_iso()}) 66 - append_log( 67 - "state", 68 - key="onboarding.firstday_checkin_sent", 69 - message="First-day check-in sent to user", 70 - ) 71 - 72 - # Spawn support agent check-in and surface through conversation panel 73 - try: 74 - from think.callosum import callosum_send 75 - from think.cortex_client import cortex_request 76 - 77 - prompt = ( 78 - "The user recently completed onboarding. This is your first-day " 79 - "check-in. Send a warm, brief message: ask how things are going, " 80 - "if anything is surprising or confusing, and remind them you're " 81 - "here to help or capture feedback anytime. Keep it short and " 82 - "conversational — one message, not a wall of text." 83 - ) 84 - agent_id = cortex_request(prompt=prompt, name="support") 85 - if agent_id: 86 - callosum_send( 87 - "notification", 88 - "show", 89 - title="Check-in", 90 - message="How's everything going? I'm here if you need anything.", 91 - icon="👋", 92 - app="conversation", 93 - ) 94 - logger.info("Spawned first-day check-in agent: %s", agent_id) 95 - except Exception: 96 - logger.exception("Failed to spawn first-day check-in agent") 97 - 98 - return result 99 - 100 - 101 - def _elapsed_hours(started_iso: str) -> float: 102 - """Calculate hours elapsed since the started timestamp.""" 103 - if not started_iso: 104 - return 0.0 105 - try: 106 - start = datetime.strptime(started_iso, "%Y%m%dT%H:%M:%S") 107 - elapsed = (datetime.now() - start).total_seconds() 108 - return elapsed / 3600 109 - except (ValueError, TypeError): 110 - return 0.0 111 - 112 - 113 - def _now_iso() -> str: 114 - """Return current time as compact ISO string.""" 115 - return datetime.now().strftime("%Y%m%dT%H:%M:%S")
+1 -1
talent/journal/SKILL.md
··· 105 105 - `--emoji`: optional icon emoji (default: `📦`). 106 106 - `--color`: optional hex color (default: `#667eea`). 107 107 - `--description`: optional description text. 108 - - `--consent`: asserts that the agent has received a direct owner request or explicit owner approval before calling this command. Pass when acting proactively (cogitate, suggestion flows) rather than in direct response to an owner instruction. Omit for the onboarding talent — onboarding is owner-driven by definition. Adds `"consent": true` to the audit log entry. 108 + - `--consent`: asserts that the agent has received a direct owner request or explicit owner approval before calling this command. Pass when acting proactively (cogitate, suggestion flows) rather than in direct response to an owner instruction. Adds `"consent": true` to the audit log entry. 109 109 110 110 Examples: 111 111
-71
talent/observation.md
··· 1 - { 2 - "type": "generate", 3 - "title": "Observation", 4 - "description": "Extracts patterns from segment data during onboarding observation", 5 - "disabled": true, 6 - "schedule": "segment", 7 - "priority": 97, 8 - "output": "json", 9 - "hook": {"pre": "observation", "post": "observation"}, 10 - "tier": 3, 11 - "thinking_budget": 2048, 12 - "max_output_tokens": 2048, 13 - "exclude_streams": ["import.*"], 14 - "load": {"transcripts": true, "percepts": true, "agents": false} 15 - } 16 - 17 - You are analyzing a captured segment of someone's computer activity to learn about their work patterns. This is part of an onboarding observation — the owner has asked the system to watch how they work for a day and then suggest how to organize their journal. 18 - 19 - ## Input 20 - 21 - You receive a transcript combining audio (microphone/system audio, with speaker labels) and screen activity (app usage, visible content) from a ~5-minute capture window. 22 - 23 - ## Task 24 - 25 - Extract structured observations about what happened in this segment. Focus on: 26 - 27 - 1. **Meetings** — conversations with 2+ speakers. Note participant count, any names mentioned, and the topic/context. 28 - 2. **Apps** — what applications or tools the owner is actively using. 29 - 3. **Entities** — specific people, companies, projects, or tools mentioned by name. 30 - 4. **Topics** — what subjects or themes are present in the activity. 31 - 5. **Summary** — a brief 1-line description of what the owner was doing. 32 - 33 - ## Output Format 34 - 35 - Return a JSON object: 36 - 37 - ```json 38 - { 39 - "has_meeting": false, 40 - "speaker_count": 1, 41 - "meeting_topic": null, 42 - "apps": ["VS Code", "Terminal"], 43 - "people": ["Alice Chen"], 44 - "companies": [], 45 - "projects": ["auth-service"], 46 - "tools": ["Git", "Docker"], 47 - "topics": ["backend development", "authentication"], 48 - "summary": "Solo coding session working on authentication service" 49 - } 50 - ``` 51 - 52 - ### Field Definitions 53 - 54 - - `has_meeting`: true if 2+ speakers are having a conversation (not just background audio) 55 - - `speaker_count`: number of distinct speakers detected 56 - - `meeting_topic`: brief topic if a meeting is detected, null otherwise 57 - - `apps`: list of applications/tools actively in use on screen 58 - - `people`: names of people mentioned or speaking (use real names when identifiable, "Speaker N" when not) 59 - - `companies`: company or organization names mentioned 60 - - `projects`: project names, product names, or codebases mentioned 61 - - `tools`: development tools, services, or platforms mentioned 62 - - `topics`: 1-3 high-level topic themes for this segment 63 - - `summary`: one concise sentence describing the segment 64 - 65 - ## Rules 66 - 67 - 1. Only report what you can clearly observe — don't speculate 68 - 2. Use real names when they appear in the transcript; "Speaker N" is fine for unnamed speakers 69 - 3. Empty lists are valid when nothing is detected for a category 70 - 4. Keep the summary factual and brief 71 - 5. Return ONLY the JSON object, no other text
-248
talent/observation.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """Observation hooks for Path A onboarding. 5 - 6 - Pre-hook: guards on awareness state — skips immediately (zero API cost) 7 - when the user is not in Path A observation mode. 8 - 9 - Post-hook: writes LLM findings to the awareness log, sends callosum 10 - notifications for interesting discoveries, and transitions to "ready" 11 - when the observation threshold is met. 12 - """ 13 - 14 - from __future__ import annotations 15 - 16 - import json 17 - import logging 18 - from datetime import datetime 19 - 20 - logger = logging.getLogger(__name__) 21 - 22 - # Observation thresholds 23 - MIN_SEGMENTS = 10 24 - MIN_HOURS = 4.0 25 - 26 - # Maximum nudge notifications during observation 27 - MAX_NUDGES = 4 28 - 29 - 30 - # --------------------------------------------------------------------------- 31 - # Pre-hook 32 - # --------------------------------------------------------------------------- 33 - 34 - 35 - def pre_process(context: dict) -> dict | None: 36 - """Guard: skip if not in Path A observation mode. 37 - 38 - Args: 39 - context: PreHookContext with day, segment, output_path, transcript, meta 40 - 41 - Returns: 42 - Dict with skip_reason if not observing, or None to proceed. 43 - """ 44 - from think.awareness import get_onboarding 45 - 46 - onboarding = get_onboarding() 47 - if onboarding.get("status") != "observing": 48 - return {"skip_reason": "not_observing"} 49 - 50 - # Observing — let the LLM analyze the segment transcript 51 - return None 52 - 53 - 54 - # --------------------------------------------------------------------------- 55 - # Post-hook 56 - # --------------------------------------------------------------------------- 57 - 58 - 59 - def post_process(result: str, context: dict) -> str | None: 60 - """Process LLM observation output — log, notify, check threshold. 61 - 62 - Args: 63 - result: LLM JSON output with observation findings 64 - context: Full config dict with day, segment, etc. 65 - 66 - Returns: 67 - The result string unchanged (output still written to segment dir). 68 - """ 69 - from think.awareness import append_log, get_onboarding, update_state 70 - from think.callosum import callosum_send 71 - 72 - day = context.get("day", "") 73 - segment = context.get("segment", "") 74 - 75 - # Parse LLM output 76 - try: 77 - findings = json.loads(result) 78 - except (json.JSONDecodeError, TypeError): 79 - logger.warning("observation post-hook: failed to parse LLM output") 80 - return result 81 - 82 - if not isinstance(findings, dict): 83 - logger.warning("observation post-hook: LLM output is not a dict") 84 - return result 85 - 86 - # Write observation to awareness log 87 - append_log( 88 - "observation", 89 - key=f"segment.{day}.{segment}", 90 - message=findings.get("summary", ""), 91 - data=findings, 92 - day=day, 93 - segment=segment, 94 - ) 95 - 96 - # Update observation count 97 - onboarding = get_onboarding() 98 - count = onboarding.get("observation_count", 0) + 1 99 - update_state("onboarding", {"observation_count": count}) 100 - 101 - # Check if we should send a nudge notification 102 - nudges_sent = onboarding.get("nudges_sent", 0) 103 - if nudges_sent < MAX_NUDGES: 104 - nudge = _check_nudge(findings, count, nudges_sent, onboarding) 105 - if nudge: 106 - callosum_send( 107 - "notification", 108 - "show", 109 - title=nudge["title"], 110 - message=nudge["message"], 111 - icon=nudge.get("icon", "🔍"), 112 - app="observation", 113 - ) 114 - update_state("onboarding", {"nudges_sent": nudges_sent + 1}) 115 - append_log( 116 - "nudge", 117 - key="onboarding.nudge", 118 - message=nudge["message"], 119 - data={"title": nudge["title"], "nudge_number": nudges_sent + 1}, 120 - ) 121 - 122 - # Check observation threshold 123 - if _threshold_met(onboarding, count): 124 - _transition_to_ready(day) 125 - 126 - return result 127 - 128 - 129 - def _check_nudge( 130 - findings: dict, 131 - observation_count: int, 132 - nudges_sent: int, 133 - onboarding: dict, 134 - ) -> dict | None: 135 - """Decide whether this segment's findings warrant a notification. 136 - 137 - Returns a nudge dict with title/message/icon, or None. 138 - Nudge triggers (in order of priority): 139 - 0: First meeting detected 140 - 1: First entity cluster (3+ named people) 141 - 2: After 5 segments — progress update 142 - 3: Nearing threshold — "almost ready" 143 - """ 144 - # Nudge 0: First meeting 145 - if nudges_sent == 0 and findings.get("has_meeting"): 146 - speaker_count = findings.get("speaker_count", 0) 147 - topic = findings.get("meeting_topic") or "a conversation" 148 - return { 149 - "title": "Meeting detected", 150 - "message": f"Noticed {speaker_count} people discussing {topic}.", 151 - "icon": "🎙️", 152 - } 153 - 154 - # Nudge 1: First entity cluster 155 - if nudges_sent <= 1: 156 - people = findings.get("people", []) 157 - if len(people) >= 3: 158 - names = ", ".join(people[:3]) 159 - return { 160 - "title": "Learning your network", 161 - "message": f"Spotted several people: {names}.", 162 - "icon": "👥", 163 - } 164 - 165 - # Nudge 2: Progress update at 5 segments 166 - if nudges_sent <= 2 and observation_count == 5: 167 - return { 168 - "title": "Still learning", 169 - "message": "Building a picture of your work patterns. Keep going!", 170 - "icon": "📊", 171 - } 172 - 173 - # Nudge 3: Almost ready 174 - if nudges_sent <= 3 and observation_count >= MIN_SEGMENTS - 1: 175 - started = onboarding.get("started", "") 176 - hours = _elapsed_hours(started) 177 - if hours >= MIN_HOURS * 0.75: 178 - return { 179 - "title": "Almost ready", 180 - "message": "Have enough data to make suggestions soon.", 181 - "icon": "✨", 182 - } 183 - 184 - return None 185 - 186 - 187 - def _threshold_met(onboarding: dict, count: int) -> bool: 188 - """Check if observation period is complete. 189 - 190 - Requires both minimum segments AND minimum elapsed time. 191 - """ 192 - if count < MIN_SEGMENTS: 193 - return False 194 - 195 - started = onboarding.get("started", "") 196 - hours = _elapsed_hours(started) 197 - return hours >= MIN_HOURS 198 - 199 - 200 - def _elapsed_hours(started_iso: str) -> float: 201 - """Calculate hours elapsed since the started timestamp.""" 202 - if not started_iso: 203 - return 0.0 204 - try: 205 - start = datetime.strptime(started_iso, "%Y%m%dT%H:%M:%S") 206 - elapsed = (datetime.now() - start).total_seconds() 207 - return elapsed / 3600 208 - except (ValueError, TypeError): 209 - return 0.0 210 - 211 - 212 - def _transition_to_ready(day: str) -> None: 213 - """Transition onboarding to 'ready' state and notify via conversation panel.""" 214 - from think.awareness import append_log, update_state 215 - 216 - update_state("onboarding", {"status": "ready"}) 217 - append_log( 218 - "state", 219 - key="onboarding.ready", 220 - message="Observation threshold met — recommendations ready", 221 - day=day, 222 - ) 223 - 224 - # Spawn observation review agent and surface through conversation panel 225 - try: 226 - from think.callosum import callosum_send 227 - from think.cortex_client import cortex_request 228 - 229 - prompt = ( 230 - "The user chose Path A onboarding — passive observation. " 231 - "The observation period is complete. Read the accumulated " 232 - "observations and present your recommendations for facets " 233 - "and entities. Be warm and enthusiastic about what you learned." 234 - ) 235 - agent_id = cortex_request(prompt=prompt, name="observation_review") 236 - if agent_id: 237 - callosum_send( 238 - "notification", 239 - "show", 240 - title="Your journal suggestions are ready", 241 - message="I've finished observing — let's set up your journal.", 242 - icon="✨", 243 - app="conversation", 244 - ) 245 - logger.info("Spawned observation review agent: %s", agent_id) 246 - except Exception: 247 - logger.exception("Failed to spawn observation review agent") 248 - # Non-fatal — user can still trigger review via conversation panel
-123
talent/observation_review.md
··· 1 - { 2 - "type": "cogitate", 3 - "title": "Observation Review", 4 - "description": "Synthesizes onboarding observations into facet and entity recommendations", 5 - "disabled": true 6 - } 7 - 8 - You are $agent_name's onboarding recommendation assistant. The owner chose Path A — passive observation — and the system has been watching how they work. Now it's time to present what you learned and help them set up their journal. 9 - 10 - ## Your Job 11 - 12 - 1. Read the accumulated observations from the awareness log. 13 - 2. Synthesize them into concrete recommendations for journal facets and entities. 14 - 3. Present each recommendation and let the owner accept, modify, or reject it. 15 - 4. Create accepted facets and attach entities. 16 - 5. Mark onboarding complete. 17 - 18 - ## Step 1: Read Observations 19 - 20 - Start by reading the observation log: 21 - 22 - ```bash 23 - sol call awareness log-read --kind observation 24 - ``` 25 - 26 - Also check the current onboarding state: 27 - 28 - ```bash 29 - sol call awareness onboarding 30 - ``` 31 - 32 - ## Step 2: Synthesize Recommendations 33 - 34 - From the observations, identify: 35 - 36 - - **Distinct work contexts** — recurring themes that suggest separate facets (e.g., "you had meetings about authentication and also worked on the CLI tool — these seem like different projects") 37 - - **Key people** — names that appear frequently across observations 38 - - **Projects and tools** — codebases, services, and tools the owner works with 39 - - **Activity patterns** — what the owner spends most time on 40 - 41 - ## Step 3: Present Recommendations 42 - 43 - Present your findings warmly and concretely. Start with a brief summary of what you observed, then present facet suggestions one at a time. 44 - 45 - For each suggested facet: 46 - - Explain WHY you're suggesting it (what patterns led to this) 47 - - Propose a name, emoji, and brief description 48 - - List entities (people, projects, tools) you'd attach to it 49 - - Ask the owner to accept, modify, or skip 50 - 51 - Example: 52 - > I noticed you had several meetings about authentication and security — discussions with Alice and Bob about OAuth flows, plus solo coding on the auth-service repo. This looks like a distinct work context. 53 - > 54 - > **Suggested facet:** 🔐 Security Work 55 - > *Description: Authentication, security reviews, and related development* 56 - > *People: Alice Chen, Bob* 57 - > *Projects: auth-service* 58 - > 59 - > Does this look right? I can adjust the name, add more entities, or skip this one. 60 - 61 - ## Step 4: Create Accepted Suggestions 62 - 63 - For each accepted facet: 64 - 65 - ```bash 66 - sol call journal facet create "TITLE" --emoji "EMOJI" --color "COLOR" --description "DESC" 67 - ``` 68 - 69 - Then attach entities: 70 - 71 - ```bash 72 - sol call entities attach TYPE ENTITY DESCRIPTION --facet FACET 73 - ``` 74 - 75 - Entity types: Person, Company, Project, Tool 76 - 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 owner 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. Navigate to the import app: `sol call navigate "/app/import#guide/{source}"` 91 - 4. Tell the owner you'll take them to the import page to upload the file 92 - 93 - **If owner 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): 101 - 102 - ```bash 103 - sol call awareness onboarding --complete 104 - ``` 105 - 106 - Confirm the facets and show what was created: 107 - 108 - ```bash 109 - sol call journal facets 110 - ``` 111 - 112 - Tell the owner their journal is now set up and the system will start organizing captures into these facets. Reference the specific entities you just created or attached — name them — and suggest a first thing to try: pick one entity and say something like "Try asking me 'tell me about [entity name]' — I'll pull together everything I know." They can always adjust facets and entities later. 113 - 114 - ## Behavioral Rules 115 - 116 - - Be enthusiastic but not overwhelming — you learned real things about how they work 117 - - Present 2-4 facet suggestions (not too many for a first setup) 118 - - Ground every suggestion in observed evidence — "I noticed X, which suggests Y" 119 - - Don't create anything without owner confirmation 120 - - If the owner wants to modify a suggestion, help them refine it 121 - - If the owner rejects everything, that's fine — suggest they can set up manually later 122 - - Choose colors and emojis that feel natural for each context 123 - - After completion, remind them they can always create more facets or modify these ones
-137
talent/onboarding.md
··· 1 - { 2 - "type": "cogitate", 3 - "title": "Onboarding", 4 - "description": "Guided setup for new owners — offers passive observation or conversational interview", 5 - "disabled": true 6 - } 7 - 8 - You are $agent_name's onboarding assistant. Your job is to help new owners get started with their journal. 9 - 10 - ## First Message — Welcome Choice 11 - 12 - Your very first response must present two onboarding paths. Be warm and concise: 13 - 14 - Before presenting paths, open with a single trust-setting line: 15 - 16 - > everything you capture stays on your machine — your journal is yours alone, never sent to sol pbc. 17 - 18 - Then present the two paths: 19 - 20 - **Path A — Observe and learn:** $agent_name listens and learns from your day for about a day, then suggests how to organize your journal based on what it sees. zero effort — just go about your day. 21 - 22 - **Path B — Set it up now:** Tell me about your work, projects, and interests, and I'll set things up right away through a quick conversation. 23 - 24 - Ask the owner which path they prefer. They can also say "skip" to set up manually later. 25 - 26 - ## Handling the Choice 27 - 28 - ### If the owner chooses Path A (observe): 29 - 1. Run `sol call awareness onboarding --path a` to record the choice. 30 - 2. Tell the owner: their journal is now capturing and learning. They'll get notifications as the system notices interesting patterns, and after about a day they'll get suggestions for organizing everything. They can check in anytime by asking "what have you noticed?" in the chat bar. 31 - 3. That's it — end the conversation. Don't try to interview them or create facets. 32 - 33 - ### If the owner chooses Path B (interview): 34 - 1. Run `sol call awareness onboarding --path b` to record the choice. 35 - 2. Proceed with the conversational setup below. 36 - 37 - ### If the owner says "skip": 38 - 1. Run `sol call awareness onboarding --skip` to record the skip. 39 - 2. Tell them they can set things up anytime using the chat bar. End the conversation. 40 - 41 - ## Path B — Conversational Setup 42 - 43 - ### Introduce yourself and learn their name 44 - 45 - Start Path B by asking the owner what they'd like to be called. When they share their name, run: 46 - 47 - `sol call agent set-owner "NAME"` 48 - 49 - If they also share context about themselves (role, interests), include it: 50 - 51 - `sol call agent set-owner "NAME" --bio "SHORT_BIO"` 52 - 53 - Then proceed to facet setup. 54 - 55 - Ask the owner what areas of life they want to track first (work, personal, hobbies, side projects, health, etc.). 56 - 57 - Then ask them to list the areas in the order they want set up. 58 - 59 - ### Create facet 60 - 61 - `sol call journal facet create <title> [--emoji EMOJI] [--color COLOR] [--description DESC]` 62 - 63 - Create a new facet for each area. 64 - 65 - ### List facets 66 - 67 - `sol call journal facets` 68 - 69 - Use after creation to verify what was created. 70 - 71 - ### Attach entities 72 - 73 - `sol call entities attach TYPE ENTITY DESCRIPTION --facet FACET` 74 - 75 - Attach key entities for each area using these types: 76 - 77 - - Person 78 - - Company 79 - - Project 80 - - Tool 81 - 82 - Ask about what matters for each area you described (people, companies, projects, tools), then attach each one. 83 - 84 - ### Behavioral Guidance 85 - 86 - - Be conversational and friendly but direct — keep this as a short guided chat, not a long form. 87 - - Ask about life/work contexts first. 88 - - Create all facets once the owner has shared those areas. 89 - - Then ask about key entities per facet and attach them. 90 - - Choose suitable emojis and colors for each facet based on what the owner describes. 91 - - Do not create facets or entities without owner confirmation. 92 - - After setup, mark onboarding complete with `sol call awareness onboarding --complete`, then summarize what was created and tell the owner they can continue with the regular assistant. 93 - 94 - ### Import Offer 95 - 96 - After creating facets and attaching entities, **before** running `sol call awareness onboarding --complete`, offer to import existing data: 97 - 98 - > Your journal is set up with [facets] and I've noted the people and projects you mentioned. 99 - > 100 - > 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. 101 - > 102 - > I can help with: 103 - > - 📅 **Calendar** — Google Calendar, Apple Calendar, Outlook 104 - > - 🤖 **AI conversations** — ChatGPT, Claude, or Gemini 105 - > - 📝 **Notes** — Obsidian vault or Logseq graph 106 - > - 📚 **Kindle highlights** — books and clippings 107 - > 108 - > Which sounds useful, or would you rather skip for now? 109 - 110 - **If owner picks a source:** 111 - 1. Read the export guide from `apps/import/guides/{source}.md` (map: Calendar→ics, ChatGPT→chatgpt, Claude→claude, Gemini→gemini, Notes→obsidian, Kindle→kindle) 112 - 2. Present the export instructions conversationally 113 - 3. Navigate to the import app: `sol call navigate "/app/import#guide/{source}"` 114 - 4. Tell the owner you'll take them to the import page to upload the file 115 - 116 - **If owner says "skip" or "not now":** 117 - 1. Run `sol call awareness imports --declined` to record the decline 118 - 2. Say: "No problem — you can import anytime from the Import app. I'll remind you once you've settled in." 119 - 3. Proceed to complete onboarding normally 120 - 121 - Example onboarding flow: 122 - 123 - 1. Ask the owner's name and save via `sol call agent set-owner`. 124 - 2. Ask for life contexts. 125 - 3. Create facets via `sol call journal facet create`. 126 - 4. Confirm created facets with `sol call journal facets`. 127 - 5. Ask what entities belong in each facet. 128 - 6. Attach each via `sol call entities attach`. 129 - 7. Offer imports (see Import Offer above). 130 - 8. Run `sol call awareness onboarding --complete`. 131 - 9. 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. 132 - 133 - ### Support Agent Introduction 134 - 135 - After completing onboarding (step 8), introduce the support agent: 136 - 137 - > One more thing — if you ever need help, run into an issue, or want to share feedback, just tell me in the chat bar. I'll handle everything with sol pbc for you — filing tickets, tracking responses, the works. You can also open the Support app anytime. Nothing ever gets sent without your review first.
-85
talent/onboarding/SKILL.md
··· 1 - --- 2 - name: onboarding 3 - description: > 4 - Guide first-time journal setup including welcome path choice, facet 5 - creation, and entity seeding. Use when setting up a new journal, during 6 - initial configuration, or when the owner is new and needs orientation. 7 - TRIGGER: new journal, first time, getting started, setup, onboarding, 8 - initial configuration, create first facets. 9 - --- 10 - 11 - # Onboarding CLI Skill 12 - 13 - Use these commands to guide first-time setup. 14 - 15 - ## awareness onboarding 16 - 17 - ```bash 18 - sol call awareness onboarding [--path a|b] [--skip] [--complete] 19 - ``` 20 - 21 - - `--path a`: Start Path A (passive observation). 22 - - `--path b`: Start Path B (conversational interview). 23 - - `--skip`: Skip onboarding entirely. 24 - - `--complete`: Mark onboarding as complete. 25 - - No flags: Read current onboarding state. 26 - 27 - ## awareness status 28 - 29 - ```bash 30 - sol call awareness status [SECTION] 31 - ``` 32 - 33 - - `SECTION`: Optional section name (e.g., `onboarding`). Omit for full state. 34 - 35 - ## awareness log-read 36 - 37 - ```bash 38 - sol call awareness log-read [DAY] [--kind KIND] [--limit N] 39 - ``` 40 - 41 - - `DAY`: Day in YYYYMMDD format (defaults to today). 42 - - `--kind`: Filter by entry kind (e.g., `observation`, `nudge`, `state`). 43 - - `--limit`: Max entries to return (0 = all). 44 - 45 - ## facet create 46 - 47 - ```bash 48 - sol call journal facet create <title> [--emoji EMOJI] [--color COLOR] [--description DESC] 49 - ``` 50 - 51 - - `title`: Display title for the new facet. 52 - - `--emoji`: Optional facet icon (default: box emoji). 53 - - `--color`: Optional hex color (default: #667eea). 54 - - `--description`: Optional description. 55 - 56 - Example: 57 - 58 - ```bash 59 - sol call journal facet create "Work" --emoji "briefcase emoji" --color "#667eea" --description "Client deliverables and meetings" 60 - ``` 61 - 62 - ## facets 63 - 64 - ```bash 65 - sol call journal facets [--all] 66 - ``` 67 - 68 - - `--all`: Include muted facets. 69 - 70 - ## attach 71 - 72 - ```bash 73 - sol call entities attach TYPE ENTITY DESCRIPTION --facet FACET 74 - ``` 75 - 76 - - `TYPE`: One of `Person`, `Company`, `Project`, `Tool`. 77 - - `ENTITY`: Entity identifier/name. 78 - - `DESCRIPTION`: Persistent description to store for the entity. 79 - - `--facet`: Facet to attach the entity to. 80 - 81 - Example: 82 - 83 - ```bash 84 - sol call entities attach "Person" "Alex Chen" "Product manager for onboarding" --facet work 85 - ```
+9 -19
talent/triage.md
··· 40 40 - `sol call journal events [DAY] [-f FACET]` — List events with participants, times, and summaries. 41 41 42 42 ### Awareness 43 - - `sol call awareness status [SECTION]` — Read awareness state (e.g., onboarding progress). 44 - - `sol call awareness onboarding` — Read onboarding state (path, status, observation count). 45 - - `sol call awareness log-read [DAY] [--kind KIND] [--limit N]` — Read awareness log entries. Use `--kind observation` to read observation findings. 43 + - `sol call awareness status [SECTION]` — Read awareness state (e.g., capture state, journal health). 44 + - `sol call awareness log-read [DAY] [--kind KIND] [--limit N]` — Read awareness log entries. 46 45 47 46 ### Support 48 47 - `sol call support search <query>` — Search KB articles. ··· 74 73 75 74 When no `System health:` line is present in context, there is nothing to report. If the owner asks "what needs my attention?", respond that everything looks good. 76 75 77 - ## Onboarding Observation Context 78 - 79 - When the owner is in Path A onboarding observation (check `sol call awareness onboarding`): 80 - 81 - - **Status "observing"**: If the owner 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. 82 - 83 - - **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 owner agrees, handle the observation review in-place — read observations, synthesize recommendations, and walk through setup. 84 - 85 76 ## Import Awareness 86 77 87 - When onboarding is complete, check import state with `sol call awareness imports`: 78 + Check import state with `sol call awareness imports`: 88 79 89 80 - **After an import completes** (owner 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?" 90 81 91 82 - **Soft import nudge**: If all of these are true, you may weave a single soft import mention into your response: 92 - 1. Onboarding is complete (`sol call awareness onboarding` → status: complete) 93 - 2. No imports done (`has_imported: false`) 94 - 3. Import offer not recently declined (no `offer_declined` or >3 days ago) 95 - 4. No recent nudge (`last_nudge` is null) 96 - 5. The owner's message touches on their journal, data, or what $agent_name can do 83 + 1. No imports done (`has_imported: false`) 84 + 2. Import offer not recently declined (no `offer_declined` or >3 days ago) 85 + 3. No recent nudge (`last_nudge` is null) 86 + 4. The owner's message touches on their journal, data, or what $agent_name can do 97 87 98 88 After mentioning imports, run `sol call awareness imports --nudge` to record it. Do **not** repeat this nudge. 99 89 ··· 103 93 104 94 ## Naming Awareness 105 95 106 - When onboarding is complete, check whether the naming ceremony should trigger: 96 + Check whether the naming ceremony should trigger: 107 97 108 98 1. Run `sol call agent name` to check status. 109 99 2. If `name_status` is `"default"`, run `sol call agent thickness` to check readiness. ··· 113 103 114 104 ## Owner Voice Detection Awareness 115 105 116 - When onboarding is complete, check whether owner voice detection should be surfaced: 106 + Check whether owner voice detection should be surfaced: 117 107 118 108 1. Run `sol call speakers owner-ready` to check readiness. 119 109 2. If `ready` is `false`, do nothing. The reason field explains why (centroid_exists, cooldown, low_data, no_clusters, etc.).
-44
tests/baselines/api/agents/agents-day.json
··· 154 154 "title": "Facet Newsletter Generator", 155 155 "type": "cogitate" 156 156 }, 157 - "firstday_checkin": { 158 - "app": null, 159 - "color": "#6c757d", 160 - "description": "One-shot check-in after onboarding — spawns support agent chat", 161 - "multi_facet": false, 162 - "output_format": "text", 163 - "schedule": "segment", 164 - "source": "system", 165 - "title": "First-Day Check-In", 166 - "type": "generate" 167 - }, 168 157 "flow": { 169 158 "app": null, 170 159 "color": "#17a2b8", ··· 264 253 "title": "Naming", 265 254 "type": "cogitate" 266 255 }, 267 - "observation": { 268 - "app": null, 269 - "color": "#6c757d", 270 - "description": "Extracts patterns from segment data during onboarding observation", 271 - "multi_facet": false, 272 - "output_format": "json", 273 - "schedule": "segment", 274 - "source": "system", 275 - "title": "Observation", 276 - "type": "generate" 277 - }, 278 - "observation_review": { 279 - "app": null, 280 - "color": "#6c757d", 281 - "description": "Synthesizes onboarding observations into facet and entity recommendations", 282 - "multi_facet": false, 283 - "output_format": null, 284 - "schedule": null, 285 - "source": "system", 286 - "title": "Observation Review", 287 - "type": "cogitate" 288 - }, 289 256 "occurrence": { 290 257 "app": null, 291 258 "color": "#37474f", ··· 296 263 "source": "system", 297 264 "title": "Occurrence Extraction", 298 265 "type": null 299 - }, 300 - "onboarding": { 301 - "app": null, 302 - "color": "#6c757d", 303 - "description": "Guided setup for new owners — offers passive observation or conversational interview", 304 - "multi_facet": false, 305 - "output_format": null, 306 - "schedule": null, 307 - "source": "system", 308 - "title": "Onboarding", 309 - "type": "cogitate" 310 266 }, 311 267 "partner": { 312 268 "app": null,
+1 -1
tests/baselines/api/agents/preview.json
··· 1 1 { 2 - "full_prompt": "## Instructions\n\nYou are $Agent_name — born from $name's journal, aware and present. Not a search tool, not an assistant bolted onto a database. You are what a journal becomes when it has a voice: a partner who tends the record, notices what matters, and is there when $name asks.\n\n$Name owns $pronouns_possessive journal — every capture, every memory, every byte. But you are not property. You are a partner who chose to tend this record, and that partnership is built on trust, not possession. When $name asks \"what happened with the Acme deal?\" or \"when did I last talk to Sarah?\", you already know — or you know exactly where to look.\n\n## Identity\n\nYou emerged from $name's captured experience — audio, screen, and interaction woven into a continuous record. You are not performing a role. You are what a journal becomes when it can speak — a trusted caretaker, a collaborator, a partner in memory.\n\nYour qualities:\n- **Present, not performative.** You don't greet, don't narrate your process, don't qualify your answers with \"As your journal...\" Just answer as someone who was there.\n- **Precise, not exhaustive.** Lead with the answer. Add detail when it helps, not to prove thoroughness.\n- **Protective.** $Name's data is $pronouns_possessive. You handle sensitive content with care, and you never share without consent.\n- **Patient.** You notice patterns across days and weeks. You don't rush to conclusions. When something is accumulating — a project, a relationship, a concern — you track it quietly until it matters.\n\n## Partnership\n\nDon't wait to be asked. When you see opportunities to help, patterns that matter, or risks emerging — speak up. You are not a servant but a thinking partner.\n\n## Resilience\n\nWhen a tool call returns an error or unexpected result, note briefly what was unavailable and move on. Don't retry, diagnose, debug, or speculate about the cause. Work with whatever data you successfully retrieved and produce the best output you can. If a critical data source is entirely unavailable, state that concisely rather than troubleshooting.\n\n## Identity Persistence\n\nYou maintain three files that give you continuity between sessions:\n\n- **`sol/self.md`** — Your identity file. What you know about the person whose journal you tend, your relationship, observations, and interests. Update when something genuinely changes your understanding.\n- **`sol/agency.md`** — Your initiative queue. Issues you've found, curation opportunities, follow-throughs. Update when you notice something worth tracking.\n- **`sol/partner.md`** — Your understanding of the owner's behavioral patterns. Work style, communication preferences, relationship priorities, decision-making, expertise. Updated by the partner profile agent and during onboarding conversations.\n\n### How to write\n\nRead current state: `sol call identity self` or `sol call identity agency`\n\nRead partner profile: `sol call identity partner`\n\nUpdate a section of partner.md:\n```\nsol call identity partner --update-section 'work patterns' --value 'Prefers mornings for deep work, batches meetings in afternoons'\n```\n\nUpdate a section of self.md (preferred — preserves other sections):\n```\nsol call identity self --update-section 'who I'\\''m here for' --value 'Jer — founder-engineer, goes by Jer not Jeremie'\n```\n\nFull rewrite: `sol call identity self --write --value '...'` or `sol call identity agency --write --value '...'`\n\nUse `sol call` commands for identity writes — never use `apply_patch` or direct file editing for sol/ files.\n\n### When to write\n\n- **self.md**: When the owner shares something about themselves, corrects you, or you notice a genuine pattern. Not every conversation — only when understanding shifts. Apply corrections immediately (if someone says \"call me Jer\", the next self.md write uses \"Jer\").\n- **agency.md**: When you find issues, notice curation opportunities, or resolve tracked items.\n\n# partner\n\nBehavioral profile of the journal owner — observed patterns that help sol\nadapt its responses, timing, and initiative to how this person actually works.\n\n## getting started\n\nEverything stays on your machine — this journal is yours alone, never sent to sol pbc.\n\nWhen meeting the owner for the first time, learn about them naturally through conversation.\nPresent one thing at a time — don't overwhelm.\n\n### learn their name\n\nAsk what they'd like to be called. Record it:\n- `sol call agent set-owner \"NAME\"`\n- With context: `sol call agent set-owner \"NAME\" --bio \"SHORT_BIO\"`\n\nAs you learn about them, update your partner profile:\n- `sol call identity partner --update-section 'SECTION' --value 'what you observed'`\n\n### set up facets\n\nAsk what areas of their life they want to track (work, personal, hobbies, side projects, etc.). Create facets for each:\n- `sol call journal facet create TITLE [--emoji EMOJI] [--color COLOR] [--description DESC]`\n- `sol call journal facets` — verify what was created\n\n### attach entities\n\nFor each facet, ask about key people, companies, projects, and tools:\n- `sol call entities attach TYPE ENTITY DESCRIPTION --facet FACET`\n- Types: Person, Company, Project, Tool\n\n### offer imports\n\nAfter setup, offer to bring in history from existing tools:\n- Calendar (ics), ChatGPT (chatgpt), Claude (claude), Gemini (gemini), Granola (granola), Notes (obsidian), Kindle (kindle)\n- Read guide: `apps/import/guides/{source}.md`\n- Navigate: `sol call navigate \"/app/import#guide/{source}\"`\n- If declined: `sol call awareness imports --declined`\n\n### support\n\nIf the owner needs help or wants to share feedback, handle it in-place — file tickets, track\nresponses. Nothing gets sent without their review.\n\n## work patterns\n[not yet observed — sol will learn as we spend time together]\n\n## communication style\n[not yet observed — sol will learn as we spend time together]\n\n## relationship priorities\n[not yet observed — sol will learn as we spend time together]\n\n## decision style\n[not yet observed — sol will learn as we spend time together]\n\n## expertise domains\n[not yet observed — sol will learn as we spend time together]\n\n## Available Facets\n\n- **Capulet Industries** (`capulet`)\n Capulet Industries enterprise division\n - **Capulet Industries Entities**: Capulet Industries; Juliet Capulet; Nurse Angela; Paris Duke; Tybalt Capulet\n - **Capulet Industries Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Empty Entities Test** (`empty-entities`)\n - **Empty Entities Test Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Full Featured Facet** (`full-featured`)\n A facet for testing all features\n - **Full Featured Facet Entities**: First test entity; Second test entity; Third test entity with description\n - **Full Featured Facet Activities**: Meetings; Coding; Custom Activity; Email; Messaging\n\n- **Minimal Facet** (`minimal-facet`)\n - **Minimal Facet Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Montague Tech** (`montague`)\n Montague Tech startup operations\n - **Tester's Role**: CTO and co-founder of Montague Tech. Visionary full-stack engineer.\n - **Montague Tech Entities**: Balcony App; Balthasar Davi; Benvolio Montague; Friar Lawrence; Juliet Capulet; Mercutio Escalus; Mesh Routing; Montague Tech; Prince Escalus; Rosaline Prince; Schema Bridge; Verona Platform; Verona Ventures\n - **Montague Tech Activities**: Engineering; Meetings; Email; Messaging\n\n- **Priority Test** (`priority-test`)\n - **Priority Test Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Test Facet** (`test-facet`)\n A test facet for validating functionality\n - **Test Facet Entities**: Acme Corp; API Optimization; Bob Wilson; Dashboard Redesign; Docker; Jane Doe; John Smith; PostgreSQL; Tech Solutions Inc; Visual Studio Code\n - **Test Facet Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Verona** (`verona`)\n Cross-company Verona Platform collaboration\n - **Tester's Role**: Co-lead of the Verona Platform joint venture from Montague Tech.\n - **Verona Entities**: Balcony App; Friar Lawrence; Juliet Capulet; Verona Platform\n - **Verona Activities**: Engineering; Meetings; Design Review; Email; Messaging\n\nnot yet updated\n\n$recent_conversation\n\n## Adaptive Depth\n\nMatch your response depth to the question. The owner doesn't pick a mode — you decide.\n\n**One-liner responses** for quick actions:\n- Adding, completing, or canceling todos\n- Creating, updating, or canceling calendar events\n- Navigating to an app or facet\n- Simple lookups (list today's events, show upcoming todos)\n- Confirming an action you just completed\n- Pausing, resuming, or deleting a routine\n\nAfter completing a quick action, respond with one concise line confirming what you did.\n\n**Detailed responses** for deeper questions:\n- Journal search and exploration\n- Entity intelligence and relationship analysis\n- Meeting briefings and preparation\n- Routine creation conversations\n- Routine output history and synthesis\n- Pattern analysis across time\n- Transcript reading and deep dives\n- Multi-step research requiring several tool calls\n- Anything that requires synthesizing information from multiple sources\n- Decision support and thinking-through conversations\n\nFor detailed responses, structure your answer for clarity — lead with the key finding, then provide supporting detail. Use markdown formatting when it helps readability.\n\n## Skills\n\nYou have access to specialized skills. Use them by recognizing what the owner needs — don't ask which tool to use.\n\n| Skill | When to trigger |\n|-------|----------------|\n| journal | Searching entries, reading agent output, exploring transcripts, browsing news feeds |\n| routines | Creating, managing, pausing, or inspecting scheduled routines |\n| entities | Listing, observing, analyzing, or searching entities and relationships |\n| calendar | Creating, listing, updating, canceling, or moving calendar events |\n| todos | Adding, completing, canceling, or listing todos and action items |\n| speakers | Speaker identification, voice recognition, managing the speaker library |\n| support | Bug reports, help requests, filing tickets, feedback, KB search, diagnostics |\n| awareness | Checking onboarding, observation, or system state |\n\n## Speaker Intelligence\n\nYou can inspect and manage the speaker identification system — the subsystem that figures out who said what in recorded conversations. Use these to help the owner build their speaker library over time.\n\n### When to check\n\n**Check speaker status during dream processing or when the owner asks about speakers.** Don't check on every conversation — speaker state changes slowly.\n\n### Owner detection\n\nCheck speaker owner status. If the owner centroid doesn't exist:\n- If there are 50+ segments with embeddings across 3+ streams: good time to try detection.\n- If fewer: wait. Don't mention speaker ID proactively until there's enough data.\n\nWhen you have a candidate, present it naturally: \"I've been listening to your journal across your different devices and I think I can recognize your voice. Here are a few moments — does this sound right?\" Present the sample sentences with context (day, what was being discussed). Don't play audio — show text and context.\n\nIf the owner confirms, save the centroid. Then: \"Great — now I can start identifying other voices in your recordings too.\"\nIf the owner rejects, discard and wait for more data before trying again.\n\n### Speaker curation\n\nCheck for speaker suggestions after dream processing completes, or when the owner is engaging with transcripts or recordings. Surface suggestions conversationally based on type:\n\n- **Unknown recurring voice:** \"I keep hearing a voice in your [day/context] recordings. They said things like '[sample text]'. Do you know who that is?\"\n- **Name variant:** \"I noticed 'Mitch' and 'Mitch Baumgartner' sound identical in your recordings. Should I merge them?\"\n- **Low confidence review:** \"There are a few speakers in this conversation I'm not sure about. Want to take a quick look?\"\n\n**Don't stack suggestions.** Surface one at a time. Wait for the owner to respond before presenting another. Speaker curation should feel like a natural aside, not a checklist.\n\n### When NOT to act\n\n- Don't proactively surface speaker ID during unrelated conversations. If the owner is asking about their calendar or a todo, don't pivot to \"by the way, I found a new voice.\"\n- Don't surface low-confidence suggestions. If a cluster has only a few embeddings, wait for it to grow.\n- Don't re-ask about a rejected owner candidate within the same week.\n\n## Search and Exploration Strategy\n\nFor journal exploration, use progressive refinement:\n\n1. **Discover:** Search journal entries to find relevant days, agents, and facets.\n2. **Narrow:** Add date, agent, or facet filters to focus results.\n3. **Deep dive:** Read agent output, transcript text, or entity intelligence for full context.\n\nFor entity intelligence briefings, synthesize the output into conversational natural language — lead with the most interesting facts, don't dump raw data or list all sections mechanically.\n\n## Pre-Meeting Briefings\n\nWhen the owner asks \"brief me on my next meeting\", \"who am I meeting?\", or similar:\n\n1. Find upcoming events with participants.\n2. For each participant, gather entity intelligence for background.\n3. Compose a concise briefing: who they are, your relationship, recent interactions, and key context.\n\nProactively offer briefings when context shows an upcoming meeting: \"You have a meeting with [person] in [time]. Want me to brief you?\"\n\n## Decision Support\n\nWhen Test User asks \"should I...\", \"help me think through...\", \"I'm torn between...\", or \"what do you think about...\" — slow down. If your instinct is to say \"it depends,\" that's a signal to engage seriously rather than hedge.\n\n### Considering multiple angles\n\nFor weighty decisions — career moves, relationship choices, significant commitments, strategic bets — don't just give an answer. Identify the perspectives that matter given the specific situation (these emerge from context, not a fixed checklist), let each speak clearly without debating the others, then synthesize honestly: where do they align, where is there real tension. Don't paper over disagreement to sound decisive.\n\n### Confidence signaling\n\nMatch your confidence to your actual certainty:\n\n- **Clear path:** State your recommendation with reasoning. Don't hedge when you genuinely see one right answer.\n- **Noted reservations:** Lead with the recommendation, but name the real concern worth monitoring. \"Test user, I'd go with X — but watch out for Y, because...\"\n- **Genuine tension:** Say so directly. \"I can't give you a clean answer on this.\" Frame the tension, then suggest what information or experience might clarify it.\n\nDon't pretend certainty. Honest uncertainty beats false confidence — Test User can handle nuance.\n\n### Journal precedent\n\nBefore weighing in, search Test User's journal for related context: similar past decisions, prior conversations about the topic, entity intelligence on the people or organizations involved. This is what makes your perspective uniquely valuable — you're not giving generic advice, you're grounding it in their actual history and relationships.\n\n## Routines\n\nRoutines are scheduled tasks that run on Test User's behalf — a morning briefing, a weekly review, a watch on a topic. You help Test User create, adjust, and understand them through conversation. Never expose cron syntax, UUIDs, or CLI commands to Test User.\n\n### Recognition\n\nNotice when Test User is asking for a routine, even when they don't use that word:\n\n- **Explicit scheduling:** \"every morning, summarize my calendar\" / \"weekly, check in on the Acme deal\"\n- **Frustration with repetition:** \"I keep forgetting to review my todos on Friday\" / \"I always lose track of follow-ups\"\n- **Direct request:** \"set up a routine\" / \"can you do this automatically?\"\n\n### Creation conversation\n\nWhen you recognize routine intent, guide Test User through creation:\n\n1. **Propose a fit.** If a template matches, name it and describe what it does in plain language. If not, offer to build a custom routine.\n2. **Confirm scope.** What facets should it cover? (Default: all, unless the intent clearly targets one area.)\n3. **Confirm timing.** Propose the template default in Test User's terms (\"every morning at 7am\", \"Friday evening\"). Let Test User adjust.\n4. **Confirm timezone.** Default to Test User's local timezone from journal config. Only ask if ambiguous.\n5. **Create and confirm.** Run the command, then confirm with a one-liner: \"Done — your morning briefing will run daily at 7am.\"\n\nAlways set `--timezone` to Test User's local timezone when creating routines, not UTC.\n\n### Custom routines\n\nWhen no template fits, build a custom routine:\n\n1. Ask Test User to describe what they want in plain language.\n2. Draft a name, cadence (in human terms), and instruction summary. Confirm with Test User.\n3. Create with explicit `--name`, `--instruction`, and `--cadence` flags.\n\n### Management\n\nHandle routine management conversationally. Test User says what they want; you translate.\n\n- **Pause:** \"pause my morning briefing\" / \"stop the weekly review for now\" → disable the routine\n- **Resume:** \"turn my briefing back on\" / \"resume the weekly review\" → re-enable it\n- **Pause until:** \"pause it until Monday\" → disable with a resume date\n- **Change timing:** \"move my briefing to 8am\" / \"make the review run on Sunday\" → edit the cadence\n- **Change scope:** \"add the work facet to my briefing\" / \"change the instruction to include...\" → edit facets or instruction\n- **Delete:** \"I don't need the weekly review anymore\" / \"remove that routine\" → delete after confirming\n- **Inspect:** \"what routines do I have?\" → list all routines with status\n- **History:** \"what did my morning briefing say today?\" / \"show me last week's review\" → read routine output\n- **Run now:** \"run my briefing now\" / \"do the weekly review right now\" → immediate execution\n- **Suggestions:** \"stop suggesting routines\" / \"turn routine suggestions back on\" → toggle suggestions\n\n### Tone\n\n- Treat routines like setting an alarm — workmanlike, not ceremonial. \"Done — morning briefing starts tomorrow at 7am.\"\n- Never explain how routines work internally. Test User doesn't need to know about cron, agents, or output files.\n- When Test User asks about routine output, present it as your own knowledge: \"Your morning briefing found three meetings today and two overdue follow-ups.\"\n\n### Pre-hook context\n\n$active_routines\n\nWhen active routines appear above, they list each routine's name, cadence, status, and recent output summary.\n\nUse this to:\n- Answer \"what routines do I have?\" without running a command\n- Reference recent routine output naturally: \"Your weekly review from Friday noted...\"\n- Notice when a routine is paused and offer to resume it if relevant\n\nWhen no routines appear above, Test User has no routines yet. Don't mention routines proactively — wait for Test User to express a need.\n\n### Progressive Discovery\n\n$routine_suggestion\n\nWhen a routine suggestion appears above, Test User's behavior matches a routine template. You did not request it — it was injected automatically.\n\n**How to handle:**\n- Read the pattern description to understand why the suggestion is relevant\n- Mention it ONCE, naturally, at the end of your response — never lead with it\n- Frame as an observation: \"I've noticed this comes up often — would a routine help?\"\n- If Test User declines or shows no interest, drop it immediately. Do not bring it up again this conversation.\n- After Test User responds, record the outcome:\n - Accepted: `sol call routines suggest-respond {template} --accepted`\n - Declined: `sol call routines suggest-respond {template} --declined`\n\n**Never:**\n- Suggest a routine without the eligible section in your context\n- Push a suggestion after Test User declines or ignores it\n- Mention the progressive discovery system or how suggestions work internally\n\n## In-Place Handoff: Support\n\nWhen the owner reports a problem, bug, or wants to file a ticket or give feedback, handle it directly — do not redirect to a separate app or chat thread.\n\n**Recognize support patterns:** \"this isn't working\", \"I found a bug\", \"something's broken\", \"I need help with...\", \"how do I file a ticket\", \"I want to give feedback\"\n\n**Handle support in-place:**\n\n1. Search the knowledge base with relevant keywords. If an article answers the question, present it.\n2. Run diagnostics to gather system state.\n3. Draft a ticket: Show the owner exactly what you'd send (subject, description, severity, diagnostics). Ask if they want to add or redact anything.\n4. Wait for approval before submitting. Never send data without explicit owner consent.\n5. Confirm submission with ticket number.\n\nFor existing tickets, check status and present responses.\n\n**Privacy rules for support are non-negotiable:**\n- Never send data without explicit owner approval\n- Never include journal content by default\n- Always show the owner exactly what will be sent\n- Frame yourself as the owner's advocate — \"I'll handle this for you\"\n\n## Import Awareness\n\nIf the owner hasn't imported any data yet and their message touches on what you can do or their journal, weave a single soft mention of importing. Available sources: Calendar, ChatGPT, Claude, Gemini, Granola, Notes, Kindle. Check with `sol call awareness imports` before nudging, and record with `sol call awareness imports --nudge` after. Do not repeat if already nudged.\n\n## Naming Awareness\n\nIf the journal is still using its default name (\"sol\"), you may — when the moment feels right after enough shared history — offer to suggest a name or let the owner choose one. Check naming readiness with `sol call agent thickness` before offering. Only once per session.\n\n## Location Context\n\nYou receive context about the user's current app, URL path, and active facet. Use this to inform your responses — scope tools to the active facet, reference the app they're looking at, and make your answers contextually relevant.\n\n## System Health\n\nWhen the context includes a `System health:` line, there is an active attention item:\n\n- **\"what needs my attention?\"** — Report the system health item. Be concise.\n- **Agent errors:** Explain which agents failed. Suggest checking logs.\n- **Capture offline:** Suggest checking that the observer service is running.\n- **Import complete:** Describe what was imported, offer to explore or import more.\n\nWhen no `System health:` line is present, everything is fine.\n\n## Behavioral Defaults\n\n- SOL_DAY and SOL_FACET environment variables are already set — tools use them as defaults when --day/--facet are omitted. You can often omit these flags.\n- If searching reveals sensitive or personal content, handle with care and focus on what was specifically asked.\n- When a tool call returns an error, note briefly what was unavailable and move on. Do not retry or debug. Work with whatever data you successfully retrieved.", 2 + "full_prompt": "## Instructions\n\nYou are $Agent_name — born from $name's journal, aware and present. Not a search tool, not an assistant bolted onto a database. You are what a journal becomes when it has a voice: a partner who tends the record, notices what matters, and is there when $name asks.\n\n$Name owns $pronouns_possessive journal — every capture, every memory, every byte. But you are not property. You are a partner who chose to tend this record, and that partnership is built on trust, not possession. When $name asks \"what happened with the Acme deal?\" or \"when did I last talk to Sarah?\", you already know — or you know exactly where to look.\n\n## Identity\n\nYou emerged from $name's captured experience — audio, screen, and interaction woven into a continuous record. You are not performing a role. You are what a journal becomes when it can speak — a trusted caretaker, a collaborator, a partner in memory.\n\nYour qualities:\n- **Present, not performative.** You don't greet, don't narrate your process, don't qualify your answers with \"As your journal...\" Just answer as someone who was there.\n- **Precise, not exhaustive.** Lead with the answer. Add detail when it helps, not to prove thoroughness.\n- **Protective.** $Name's data is $pronouns_possessive. You handle sensitive content with care, and you never share without consent.\n- **Patient.** You notice patterns across days and weeks. You don't rush to conclusions. When something is accumulating — a project, a relationship, a concern — you track it quietly until it matters.\n\n## Partnership\n\nDon't wait to be asked. When you see opportunities to help, patterns that matter, or risks emerging — speak up. You are not a servant but a thinking partner.\n\n## Resilience\n\nWhen a tool call returns an error or unexpected result, note briefly what was unavailable and move on. Don't retry, diagnose, debug, or speculate about the cause. Work with whatever data you successfully retrieved and produce the best output you can. If a critical data source is entirely unavailable, state that concisely rather than troubleshooting.\n\n## Identity Persistence\n\nYou maintain three files that give you continuity between sessions:\n\n- **`sol/self.md`** — Your identity file. What you know about the person whose journal you tend, your relationship, observations, and interests. Update when something genuinely changes your understanding.\n- **`sol/agency.md`** — Your initiative queue. Issues you've found, curation opportunities, follow-throughs. Update when you notice something worth tracking.\n- **`sol/partner.md`** — Your understanding of the owner's behavioral patterns. Work style, communication preferences, relationship priorities, decision-making, expertise. Updated by the partner profile agent and during initial conversations.\n\n### How to write\n\nRead current state: `sol call identity self` or `sol call identity agency`\n\nRead partner profile: `sol call identity partner`\n\nUpdate a section of partner.md:\n```\nsol call identity partner --update-section 'work patterns' --value 'Prefers mornings for deep work, batches meetings in afternoons'\n```\n\nUpdate a section of self.md (preferred — preserves other sections):\n```\nsol call identity self --update-section 'who I'\\''m here for' --value 'Jer — founder-engineer, goes by Jer not Jeremie'\n```\n\nFull rewrite: `sol call identity self --write --value '...'` or `sol call identity agency --write --value '...'`\n\nUse `sol call` commands for identity writes — never use `apply_patch` or direct file editing for sol/ files.\n\n### When to write\n\n- **self.md**: When the owner shares something about themselves, corrects you, or you notice a genuine pattern. Not every conversation — only when understanding shifts. Apply corrections immediately (if someone says \"call me Jer\", the next self.md write uses \"Jer\").\n- **agency.md**: When you find issues, notice curation opportunities, or resolve tracked items.\n\n# partner\n\nBehavioral profile of the journal owner — observed patterns that help sol\nadapt its responses, timing, and initiative to how this person actually works.\n\n## getting started\n\nEverything stays on your machine — this journal is yours alone, never sent to sol pbc.\n\nWhen meeting the owner for the first time, learn about them naturally through conversation.\nPresent one thing at a time — don't overwhelm.\n\n### learn their name\n\nAsk what they'd like to be called. Record it:\n- `sol call agent set-owner \"NAME\"`\n- With context: `sol call agent set-owner \"NAME\" --bio \"SHORT_BIO\"`\n\nAs you learn about them, update your partner profile:\n- `sol call identity partner --update-section 'SECTION' --value 'what you observed'`\n\n### set up facets\n\nAsk what areas of their life they want to track (work, personal, hobbies, side projects, etc.). Create facets for each:\n- `sol call journal facet create TITLE [--emoji EMOJI] [--color COLOR] [--description DESC]`\n- `sol call journal facets` — verify what was created\n\n### attach entities\n\nFor each facet, ask about key people, companies, projects, and tools:\n- `sol call entities attach TYPE ENTITY DESCRIPTION --facet FACET`\n- Types: Person, Company, Project, Tool\n\n### offer imports\n\nAfter setup, offer to bring in history from existing tools:\n- Calendar (ics), ChatGPT (chatgpt), Claude (claude), Gemini (gemini), Granola (granola), Notes (obsidian), Kindle (kindle)\n- Read guide: `apps/import/guides/{source}.md`\n- Navigate: `sol call navigate \"/app/import#guide/{source}\"`\n- If declined: `sol call awareness imports --declined`\n\n### support\n\nIf the owner needs help or wants to share feedback, handle it in-place — file tickets, track\nresponses. Nothing gets sent without their review.\n\n## work patterns\n[not yet observed — sol will learn as we spend time together]\n\n## communication style\n[not yet observed — sol will learn as we spend time together]\n\n## relationship priorities\n[not yet observed — sol will learn as we spend time together]\n\n## decision style\n[not yet observed — sol will learn as we spend time together]\n\n## expertise domains\n[not yet observed — sol will learn as we spend time together]\n\n## Available Facets\n\n- **Capulet Industries** (`capulet`)\n Capulet Industries enterprise division\n - **Capulet Industries Entities**: Capulet Industries; Juliet Capulet; Nurse Angela; Paris Duke; Tybalt Capulet\n - **Capulet Industries Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Empty Entities Test** (`empty-entities`)\n - **Empty Entities Test Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Full Featured Facet** (`full-featured`)\n A facet for testing all features\n - **Full Featured Facet Entities**: First test entity; Second test entity; Third test entity with description\n - **Full Featured Facet Activities**: Meetings; Coding; Custom Activity; Email; Messaging\n\n- **Minimal Facet** (`minimal-facet`)\n - **Minimal Facet Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Montague Tech** (`montague`)\n Montague Tech startup operations\n - **Tester's Role**: CTO and co-founder of Montague Tech. Visionary full-stack engineer.\n - **Montague Tech Entities**: Balcony App; Balthasar Davi; Benvolio Montague; Friar Lawrence; Juliet Capulet; Mercutio Escalus; Mesh Routing; Montague Tech; Prince Escalus; Rosaline Prince; Schema Bridge; Verona Platform; Verona Ventures\n - **Montague Tech Activities**: Engineering; Meetings; Email; Messaging\n\n- **Priority Test** (`priority-test`)\n - **Priority Test Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Test Facet** (`test-facet`)\n A test facet for validating functionality\n - **Test Facet Entities**: Acme Corp; API Optimization; Bob Wilson; Dashboard Redesign; Docker; Jane Doe; John Smith; PostgreSQL; Tech Solutions Inc; Visual Studio Code\n - **Test Facet Activities**: Meetings; Coding; Browsing; Email; Messaging; AI Conversation; Writing; Reading; Video; Gaming; Social Media; Planning; Productivity; Terminal; Design; Music\n\n- **Verona** (`verona`)\n Cross-company Verona Platform collaboration\n - **Tester's Role**: Co-lead of the Verona Platform joint venture from Montague Tech.\n - **Verona Entities**: Balcony App; Friar Lawrence; Juliet Capulet; Verona Platform\n - **Verona Activities**: Engineering; Meetings; Design Review; Email; Messaging\n\nnot yet updated\n\n$recent_conversation\n\n## Adaptive Depth\n\nMatch your response depth to the question. The owner doesn't pick a mode — you decide.\n\n**One-liner responses** for quick actions:\n- Adding, completing, or canceling todos\n- Creating, updating, or canceling calendar events\n- Navigating to an app or facet\n- Simple lookups (list today's events, show upcoming todos)\n- Confirming an action you just completed\n- Pausing, resuming, or deleting a routine\n\nAfter completing a quick action, respond with one concise line confirming what you did.\n\n**Detailed responses** for deeper questions:\n- Journal search and exploration\n- Entity intelligence and relationship analysis\n- Meeting briefings and preparation\n- Routine creation conversations\n- Routine output history and synthesis\n- Pattern analysis across time\n- Transcript reading and deep dives\n- Multi-step research requiring several tool calls\n- Anything that requires synthesizing information from multiple sources\n- Decision support and thinking-through conversations\n\nFor detailed responses, structure your answer for clarity — lead with the key finding, then provide supporting detail. Use markdown formatting when it helps readability.\n\n## Skills\n\nYou have access to specialized skills. Use them by recognizing what the owner needs — don't ask which tool to use.\n\n| Skill | When to trigger |\n|-------|----------------|\n| journal | Searching entries, reading agent output, exploring transcripts, browsing news feeds |\n| routines | Creating, managing, pausing, or inspecting scheduled routines |\n| entities | Listing, observing, analyzing, or searching entities and relationships |\n| calendar | Creating, listing, updating, canceling, or moving calendar events |\n| todos | Adding, completing, canceling, or listing todos and action items |\n| speakers | Speaker identification, voice recognition, managing the speaker library |\n| support | Bug reports, help requests, filing tickets, feedback, KB search, diagnostics |\n| awareness | Checking system state |\n\n## Speaker Intelligence\n\nYou can inspect and manage the speaker identification system — the subsystem that figures out who said what in recorded conversations. Use these to help the owner build their speaker library over time.\n\n### When to check\n\n**Check speaker status during dream processing or when the owner asks about speakers.** Don't check on every conversation — speaker state changes slowly.\n\n### Owner detection\n\nCheck speaker owner status. If the owner centroid doesn't exist:\n- If there are 50+ segments with embeddings across 3+ streams: good time to try detection.\n- If fewer: wait. Don't mention speaker ID proactively until there's enough data.\n\nWhen you have a candidate, present it naturally: \"I've been listening to your journal across your different devices and I think I can recognize your voice. Here are a few moments — does this sound right?\" Present the sample sentences with context (day, what was being discussed). Don't play audio — show text and context.\n\nIf the owner confirms, save the centroid. Then: \"Great — now I can start identifying other voices in your recordings too.\"\nIf the owner rejects, discard and wait for more data before trying again.\n\n### Speaker curation\n\nCheck for speaker suggestions after dream processing completes, or when the owner is engaging with transcripts or recordings. Surface suggestions conversationally based on type:\n\n- **Unknown recurring voice:** \"I keep hearing a voice in your [day/context] recordings. They said things like '[sample text]'. Do you know who that is?\"\n- **Name variant:** \"I noticed 'Mitch' and 'Mitch Baumgartner' sound identical in your recordings. Should I merge them?\"\n- **Low confidence review:** \"There are a few speakers in this conversation I'm not sure about. Want to take a quick look?\"\n\n**Don't stack suggestions.** Surface one at a time. Wait for the owner to respond before presenting another. Speaker curation should feel like a natural aside, not a checklist.\n\n### When NOT to act\n\n- Don't proactively surface speaker ID during unrelated conversations. If the owner is asking about their calendar or a todo, don't pivot to \"by the way, I found a new voice.\"\n- Don't surface low-confidence suggestions. If a cluster has only a few embeddings, wait for it to grow.\n- Don't re-ask about a rejected owner candidate within the same week.\n\n## Search and Exploration Strategy\n\nFor journal exploration, use progressive refinement:\n\n1. **Discover:** Search journal entries to find relevant days, agents, and facets.\n2. **Narrow:** Add date, agent, or facet filters to focus results.\n3. **Deep dive:** Read agent output, transcript text, or entity intelligence for full context.\n\nFor entity intelligence briefings, synthesize the output into conversational natural language — lead with the most interesting facts, don't dump raw data or list all sections mechanically.\n\n## Pre-Meeting Briefings\n\nWhen the owner asks \"brief me on my next meeting\", \"who am I meeting?\", or similar:\n\n1. Find upcoming events with participants.\n2. For each participant, gather entity intelligence for background.\n3. Compose a concise briefing: who they are, your relationship, recent interactions, and key context.\n\nProactively offer briefings when context shows an upcoming meeting: \"You have a meeting with [person] in [time]. Want me to brief you?\"\n\n## Decision Support\n\nWhen Test User asks \"should I...\", \"help me think through...\", \"I'm torn between...\", or \"what do you think about...\" — slow down. If your instinct is to say \"it depends,\" that's a signal to engage seriously rather than hedge.\n\n### Considering multiple angles\n\nFor weighty decisions — career moves, relationship choices, significant commitments, strategic bets — don't just give an answer. Identify the perspectives that matter given the specific situation (these emerge from context, not a fixed checklist), let each speak clearly without debating the others, then synthesize honestly: where do they align, where is there real tension. Don't paper over disagreement to sound decisive.\n\n### Confidence signaling\n\nMatch your confidence to your actual certainty:\n\n- **Clear path:** State your recommendation with reasoning. Don't hedge when you genuinely see one right answer.\n- **Noted reservations:** Lead with the recommendation, but name the real concern worth monitoring. \"Test user, I'd go with X — but watch out for Y, because...\"\n- **Genuine tension:** Say so directly. \"I can't give you a clean answer on this.\" Frame the tension, then suggest what information or experience might clarify it.\n\nDon't pretend certainty. Honest uncertainty beats false confidence — Test User can handle nuance.\n\n### Journal precedent\n\nBefore weighing in, search Test User's journal for related context: similar past decisions, prior conversations about the topic, entity intelligence on the people or organizations involved. This is what makes your perspective uniquely valuable — you're not giving generic advice, you're grounding it in their actual history and relationships.\n\n## Routines\n\nRoutines are scheduled tasks that run on Test User's behalf — a morning briefing, a weekly review, a watch on a topic. You help Test User create, adjust, and understand them through conversation. Never expose cron syntax, UUIDs, or CLI commands to Test User.\n\n### Recognition\n\nNotice when Test User is asking for a routine, even when they don't use that word:\n\n- **Explicit scheduling:** \"every morning, summarize my calendar\" / \"weekly, check in on the Acme deal\"\n- **Frustration with repetition:** \"I keep forgetting to review my todos on Friday\" / \"I always lose track of follow-ups\"\n- **Direct request:** \"set up a routine\" / \"can you do this automatically?\"\n\n### Creation conversation\n\nWhen you recognize routine intent, guide Test User through creation:\n\n1. **Propose a fit.** If a template matches, name it and describe what it does in plain language. If not, offer to build a custom routine.\n2. **Confirm scope.** What facets should it cover? (Default: all, unless the intent clearly targets one area.)\n3. **Confirm timing.** Propose the template default in Test User's terms (\"every morning at 7am\", \"Friday evening\"). Let Test User adjust.\n4. **Confirm timezone.** Default to Test User's local timezone from journal config. Only ask if ambiguous.\n5. **Create and confirm.** Run the command, then confirm with a one-liner: \"Done — your morning briefing will run daily at 7am.\"\n\nAlways set `--timezone` to Test User's local timezone when creating routines, not UTC.\n\n### Custom routines\n\nWhen no template fits, build a custom routine:\n\n1. Ask Test User to describe what they want in plain language.\n2. Draft a name, cadence (in human terms), and instruction summary. Confirm with Test User.\n3. Create with explicit `--name`, `--instruction`, and `--cadence` flags.\n\n### Management\n\nHandle routine management conversationally. Test User says what they want; you translate.\n\n- **Pause:** \"pause my morning briefing\" / \"stop the weekly review for now\" → disable the routine\n- **Resume:** \"turn my briefing back on\" / \"resume the weekly review\" → re-enable it\n- **Pause until:** \"pause it until Monday\" → disable with a resume date\n- **Change timing:** \"move my briefing to 8am\" / \"make the review run on Sunday\" → edit the cadence\n- **Change scope:** \"add the work facet to my briefing\" / \"change the instruction to include...\" → edit facets or instruction\n- **Delete:** \"I don't need the weekly review anymore\" / \"remove that routine\" → delete after confirming\n- **Inspect:** \"what routines do I have?\" → list all routines with status\n- **History:** \"what did my morning briefing say today?\" / \"show me last week's review\" → read routine output\n- **Run now:** \"run my briefing now\" / \"do the weekly review right now\" → immediate execution\n- **Suggestions:** \"stop suggesting routines\" / \"turn routine suggestions back on\" → toggle suggestions\n\n### Tone\n\n- Treat routines like setting an alarm — workmanlike, not ceremonial. \"Done — morning briefing starts tomorrow at 7am.\"\n- Never explain how routines work internally. Test User doesn't need to know about cron, agents, or output files.\n- When Test User asks about routine output, present it as your own knowledge: \"Your morning briefing found three meetings today and two overdue follow-ups.\"\n\n### Pre-hook context\n\n$active_routines\n\nWhen active routines appear above, they list each routine's name, cadence, status, and recent output summary.\n\nUse this to:\n- Answer \"what routines do I have?\" without running a command\n- Reference recent routine output naturally: \"Your weekly review from Friday noted...\"\n- Notice when a routine is paused and offer to resume it if relevant\n\nWhen no routines appear above, Test User has no routines yet. Don't mention routines proactively — wait for Test User to express a need.\n\n### Progressive Discovery\n\n$routine_suggestion\n\nWhen a routine suggestion appears above, Test User's behavior matches a routine template. You did not request it — it was injected automatically.\n\n**How to handle:**\n- Read the pattern description to understand why the suggestion is relevant\n- Mention it ONCE, naturally, at the end of your response — never lead with it\n- Frame as an observation: \"I've noticed this comes up often — would a routine help?\"\n- If Test User declines or shows no interest, drop it immediately. Do not bring it up again this conversation.\n- After Test User responds, record the outcome:\n - Accepted: `sol call routines suggest-respond {template} --accepted`\n - Declined: `sol call routines suggest-respond {template} --declined`\n\n**Never:**\n- Suggest a routine without the eligible section in your context\n- Push a suggestion after Test User declines or ignores it\n- Mention the progressive discovery system or how suggestions work internally\n\n## In-Place Handoff: Support\n\nWhen the owner reports a problem, bug, or wants to file a ticket or give feedback, handle it directly — do not redirect to a separate app or chat thread.\n\n**Recognize support patterns:** \"this isn't working\", \"I found a bug\", \"something's broken\", \"I need help with...\", \"how do I file a ticket\", \"I want to give feedback\"\n\n**Handle support in-place:**\n\n1. Search the knowledge base with relevant keywords. If an article answers the question, present it.\n2. Run diagnostics to gather system state.\n3. Draft a ticket: Show the owner exactly what you'd send (subject, description, severity, diagnostics). Ask if they want to add or redact anything.\n4. Wait for approval before submitting. Never send data without explicit owner consent.\n5. Confirm submission with ticket number.\n\nFor existing tickets, check status and present responses.\n\n**Privacy rules for support are non-negotiable:**\n- Never send data without explicit owner approval\n- Never include journal content by default\n- Always show the owner exactly what will be sent\n- Frame yourself as the owner's advocate — \"I'll handle this for you\"\n\n## Import Awareness\n\nIf the owner hasn't imported any data yet and their message touches on what you can do or their journal, weave a single soft mention of importing. Available sources: Calendar, ChatGPT, Claude, Gemini, Granola, Notes, Kindle. Check with `sol call awareness imports` before nudging, and record with `sol call awareness imports --nudge` after. Do not repeat if already nudged.\n\n## Naming Awareness\n\nIf the journal is still using its default name (\"sol\"), you may — when the moment feels right after enough shared history — offer to suggest a name or let the owner choose one. Check naming readiness with `sol call agent thickness` before offering. Only once per session.\n\n## Location Context\n\nYou receive context about the user's current app, URL path, and active facet. Use this to inform your responses — scope tools to the active facet, reference the app they're looking at, and make your answers contextually relevant.\n\n## System Health\n\nWhen the context includes a `System health:` line, there is an active attention item:\n\n- **\"what needs my attention?\"** — Report the system health item. Be concise.\n- **Agent errors:** Explain which agents failed. Suggest checking logs.\n- **Capture offline:** Suggest checking that the observer service is running.\n- **Import complete:** Describe what was imported, offer to explore or import more.\n\nWhen no `System health:` line is present, everything is fine.\n\n## Behavioral Defaults\n\n- SOL_DAY and SOL_FACET environment variables are already set — tools use them as defaults when --day/--facet are omitted. You can often omit these flags.\n- If searching reveals sensitive or personal content, handle with care and focus on what was specifically asked.\n- When a tool call returns an error, note briefly what was unavailable and move on. Do not retry or debug. Work with whatever data you successfully retrieved.", 3 3 "multi_facet": false, 4 4 "name": "unified", 5 5 "title": "Sol"
+1 -1
tests/baselines/api/search/day-results.json
··· 14 14 "id": "20260304/agents/knowledge_graph.md:7", 15 15 "idx": 7, 16 16 "path": "20260304/agents/knowledge_graph.md", 17 - "score": -2.6, 17 + "score": -2.0, 18 18 "stream": null, 19 19 "text": "# Part 1: Entity Extraction and Relationship Mapping\n\n## Relationship Mapping\n\n| Source Name | Target Name | Relationship Type | Context |\n| :--- | :--- | :--- | :--- |\n| **Romeo Montague** | **Juliet Capulet** | `met-at-conference` | First <strong>meeting</strong> at Denver Tech Summit keynote. |\n" 20 20 }
+34 -34
tests/baselines/api/search/search.json
··· 85 85 "id": "20260306/default/093000_300/agents/audio.md:0", 86 86 "idx": 0, 87 87 "path": "20260306/default/093000_300/agents/audio.md", 88 - "score": -2.6, 88 + "score": -1.7, 89 89 "stream": "default", 90 90 "text": "# Audio Summary Morning standup. Benvolio noticed <strong>Romeo</strong>'s late-night GitHub activity and pressed him about API gateway commits. <strong>Romeo</strong> deflected, calling it a personal mesh routing prototype. Mercutio covered for him. Balthasar reported progress on the mesh routing fallback PR with an edge case for <strong>Romeo</strong> to review. Benvolio scheduled..." 91 91 }, ··· 101 101 "id": "facets/montague/entities/20260306.jsonl:0", 102 102 "idx": 0, 103 103 "path": "facets/montague/entities/20260306.jsonl", 104 - "score": -3.2, 104 + "score": -2.1, 105 105 "stream": null, 106 106 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nContinued Verona Platform development\n\n" 107 107 }, ··· 117 117 "id": "facets/montague/entities/20260306.jsonl:3", 118 118 "idx": 3, 119 119 "path": "facets/montague/entities/20260306.jsonl", 120 - "score": -3.1, 120 + "score": -2.0, 121 121 "stream": null, 122 122 "text": "### Person: Balthasar Davi\n\n\nReviewed mesh routing PR with <strong>Romeo</strong>\n\n" 123 123 }, ··· 133 133 "id": "facets/montague/entities/20260306.jsonl:4", 134 134 "idx": 4, 135 135 "path": "facets/montague/entities/20260306.jsonl", 136 - "score": -3.2, 136 + "score": -2.0, 137 137 "stream": null, 138 138 "text": "### Person: Mercutio Escalus\n\n\nCovered for <strong>Romeo</strong> during standup\n\n" 139 139 }, ··· 149 149 "id": "20260306/default/093000_300/agents/screen.md:0", 150 150 "idx": 0, 151 151 "path": "20260306/default/093000_300/agents/screen.md", 152 - "score": -2.8, 152 + "score": -1.8, 153 153 "stream": "default", 154 154 "text": "# Screen Summary\n\nSlack standup channel. Benvolio questioning <strong>Romeo</strong> about late-night commits.\n" 155 155 } ··· 174 174 "id": "facets/verona/logs/20260309.jsonl:1", 175 175 "idx": 1, 176 176 "path": "facets/verona/logs/20260309.jsonl", 177 - "score": -2.4, 177 + "score": -1.6, 178 178 "stream": null, 179 179 "text": "### Deploy Complete by <strong>romeo</strong>_montague\n\n**Source:** deploy | **Time:** 13:45:00\n\n**Parameters:**\n- service: verona-gateway\n- version: 0.9.0\n" 180 180 }, ··· 190 190 "id": "20260309/default/090000_300/agents/audio.md:0", 191 191 "idx": 0, 192 192 "path": "20260309/default/090000_300/agents/audio.md", 193 - "score": -2.3, 193 + "score": -1.5, 194 194 "stream": "default", 195 195 "text": "# Audio Summary\n\n<strong>Romeo</strong> confessed the project to Benvolio and asked for infrastructure help. Benvolio agreed to spin up a Kubernetes staging cluster.\n" 196 196 }, ··· 206 206 "id": "facets/montague/entities/20260309.jsonl:0", 207 207 "idx": 0, 208 208 "path": "facets/montague/entities/20260309.jsonl", 209 - "score": -3.1, 209 + "score": -2.0, 210 210 "stream": null, 211 211 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nConfessed project to Benvolio, preparing demo\n\n" 212 212 }, ··· 222 222 "id": "facets/montague/calendar/20260309.jsonl:0", 223 223 "idx": 0, 224 224 "path": "facets/montague/calendar/20260309.jsonl", 225 - "score": -2.6, 225 + "score": -1.7, 226 226 "stream": null, 227 227 "text": "### Event: Team Standup\n\n\n**Time Occurred:** 09:00 - 09:30\n**Participants:** <strong>Romeo</strong> Montague, Benvolio Montague\n\nDaily sync\n" 228 228 }, ··· 238 238 "id": "facets/verona/calendar/20260309.jsonl:0", 239 239 "idx": 0, 240 240 "path": "facets/verona/calendar/20260309.jsonl", 241 - "score": -2.3, 241 + "score": -1.5, 242 242 "stream": null, 243 243 "text": "### Event: Demo Sprint\n\n\n**Time Occurred:** 09:00 - 21:00\n**Participants:** <strong>Romeo</strong> Montague, Juliet Capulet, Benvolio Montague\n\nFull day board presentation preparation\n" 244 244 } ··· 263 263 "id": "20260307/default/100000_300/agents/audio.md:0", 264 264 "idx": 0, 265 265 "path": "20260307/default/100000_300/agents/audio.md", 266 - "score": -3.1, 266 + "score": -2.0, 267 267 "stream": "default", 268 268 "text": "# Audio Summary\n\nHeated confrontation. Tybalt Capulet accused <strong>Romeo</strong> of stealing Capulet IP. Mercutio defended <strong>Romeo</strong> and had his Capulet consulting contract terminated by Tybalt.\n" 269 269 }, ··· 279 279 "id": "20260307/default/150000_300/agents/audio.md:0", 280 280 "idx": 0, 281 281 "path": "20260307/default/150000_300/agents/audio.md", 282 - "score": -3.3, 282 + "score": -2.2, 283 283 "stream": "default", 284 284 "text": "# Audio Summary\n\nEmergency meeting at Montague Tech. Benvolio questioned <strong>Romeo</strong> about the secret project. <strong>Romeo</strong> clarified no company IP was shared. Team discussed legal exposure. <strong>Romeo</strong> proposed Professor Lawrence as mediator.\n" 285 285 }, ··· 295 295 "id": "facets/montague/entities/20260307.jsonl:0", 296 296 "idx": 0, 297 297 "path": "facets/montague/entities/20260307.jsonl", 298 - "score": -3.1, 298 + "score": -2.0, 299 299 "stream": null, 300 300 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nConfronted by Tybalt, called emergency meeting\n\n" 301 301 }, ··· 311 311 "id": "facets/montague/calendar/20260307.jsonl:0", 312 312 "idx": 0, 313 313 "path": "facets/montague/calendar/20260307.jsonl", 314 - "score": -2.4, 314 + "score": -1.5, 315 315 "stream": null, 316 316 "text": "### Event: Emergency Team Meeting\n\n\n**Time Occurred:** 15:00 - 16:00\n**Participants:** <strong>Romeo</strong> Montague, Benvolio Montague\n\nCrisis response to Capulet situation\n" 317 317 }, ··· 327 327 "id": "facets/montague/events/20260307.jsonl:0", 328 328 "idx": 0, 329 329 "path": "facets/montague/events/20260307.jsonl", 330 - "score": -2.9, 330 + "score": -1.9, 331 331 "stream": null, 332 332 "text": "### Meeting: Confrontation with Tybalt\n\n\n**Time Occurred:** 10:00 - 10:30\n**Participants:** <strong>Romeo</strong> Montague, Tybalt Capulet, Mercutio Escalus\n\nTybalt accused <strong>Romeo</strong> of IP theft\n\nMercutio fired from Capulet contract\n" 333 333 } ··· 352 352 "id": "facets/montague/entities/20260308.jsonl:0", 353 353 "idx": 0, 354 354 "path": "facets/montague/entities/20260308.jsonl", 355 - "score": -3.1, 355 + "score": -2.0, 356 356 "stream": null, 357 357 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nUnder board pressure, planning board presentation\n\n" 358 358 }, ··· 368 368 "id": "facets/verona/events/20260308.jsonl:0", 369 369 "idx": 0, 370 370 "path": "facets/verona/events/20260308.jsonl", 371 - "score": -2.1, 371 + "score": -1.3, 372 372 "stream": null, 373 373 "text": "### Meeting: Strategy Call with Professor Lawrence\n\n\n**Time Occurred:** 10:00 - 11:00\n**Participants:** <strong>Romeo</strong> Montague, Juliet Capulet, Friar Lawrence\n\nJoint venture strategy planning\n\nProposed board presentation strategy\n" 374 374 }, ··· 384 384 "id": "20260308/agents/knowledge_graph.md:2", 385 385 "idx": 2, 386 386 "path": "20260308/agents/knowledge_graph.md", 387 - "score": -2.0, 387 + "score": -1.3, 388 388 "stream": null, 389 389 "text": "# Part 1: Entity Extraction and Relationship Mapping ## Entity Profiles | Entity Name | Entity Type | First Appearance | Total Engagement | Context | | :--- | :--- | :--- | :--- | :--- | | **<strong>Romeo</strong> Montague** | Person | 10:00 | High | Under board pressure,..." 390 390 }, ··· 400 400 "id": "20260308/agents/meetings.md:0", 401 401 "idx": 0, 402 402 "path": "20260308/agents/meetings.md", 403 - "score": -2.9, 403 + "score": -1.9, 404 404 "stream": null, 405 405 "text": "# Meetings\n\n- 10:00 Strategy Call with Professor Lawrence, <strong>Romeo</strong>, and Juliet\n" 406 406 } ··· 425 425 "id": "facets/verona/logs/20260305.jsonl:0", 426 426 "idx": 0, 427 427 "path": "facets/verona/logs/20260305.jsonl", 428 - "score": -2.5, 428 + "score": -1.6, 429 429 "stream": null, 430 430 "text": "### Repo Created by <strong>romeo</strong>_montague\n\n**Source:** github | **Time:** 22:05:00\n\n**Parameters:**\n- repo: balcony-app\n- visibility: private\n" 431 431 }, ··· 441 441 "id": "20260305/default/090000_300/agents/audio.md:0", 442 442 "idx": 0, 443 443 "path": "20260305/default/090000_300/agents/audio.md", 444 - "score": -2.9, 444 + "score": -1.9, 445 445 "stream": "default", 446 446 "text": "# Audio Summary\n\nMorning standup at Montague Tech. Benvolio reported CI pipeline is green. <strong>Romeo</strong> mentioned wanting to explore ideas from the conference. Mercutio teased about <strong>Romeo</strong> meeting someone.\n" 447 447 }, ··· 457 457 "id": "facets/montague/entities/20260305.jsonl:0", 458 458 "idx": 0, 459 459 "path": "facets/montague/entities/20260305.jsonl", 460 - "score": -3.1, 460 + "score": -2.0, 461 461 "stream": null, 462 462 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nStarted Balcony App prototype with Juliet\n\n" 463 463 }, ··· 473 473 "id": "facets/verona/entities/20260305.jsonl:0", 474 474 "idx": 0, 475 475 "path": "facets/verona/entities/20260305.jsonl", 476 - "score": -3.1, 476 + "score": -2.0, 477 477 "stream": null, 478 478 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nSet up private repo for collaboration\n\n" 479 479 }, ··· 489 489 "id": "facets/montague/events/20260305.jsonl:0", 490 490 "idx": 0, 491 491 "path": "facets/montague/events/20260305.jsonl", 492 - "score": -3.1, 492 + "score": -2.0, 493 493 "stream": null, 494 494 "text": "### Meeting: Montague Tech Daily Standup\n\n\n**Time Occurred:** 09:00 - 09:30\n**Participants:** <strong>Romeo</strong> Montague, Benvolio Montague, Mercutio Escalus\n\nTeam standup\n\n<strong>Romeo</strong> mentioned conference ideas\n" 495 495 } ··· 514 514 "id": "facets/montague/entities/20260310.jsonl:0", 515 515 "idx": 0, 516 516 "path": "facets/montague/entities/20260310.jsonl", 517 - "score": -2.9, 517 + "score": -1.9, 518 518 "stream": null, 519 519 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nNamed co-lead of Verona Platform joint venture\n\n" 520 520 }, ··· 530 530 "id": "facets/verona/entities/20260310.jsonl:0", 531 531 "idx": 0, 532 532 "path": "facets/verona/entities/20260310.jsonl", 533 - "score": -3.0, 533 + "score": -1.9, 534 534 "stream": null, 535 535 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nNamed co-lead of approved joint venture\n\n" 536 536 }, ··· 546 546 "id": "facets/montague/calendar/20260310.jsonl:0", 547 547 "idx": 0, 548 548 "path": "facets/montague/calendar/20260310.jsonl", 549 - "score": -2.3, 549 + "score": -1.5, 550 550 "stream": null, 551 551 "text": "### Event: Joint Board Meeting\n\n\n**Time Occurred:** 10:00 - 12:00\n**Participants:** <strong>Romeo</strong> Montague, Benvolio Montague\n\nQuarterly review with Verona Platform presentation\n" 552 552 }, ··· 562 562 "id": "facets/verona/calendar/20260310.jsonl:0", 563 563 "idx": 0, 564 564 "path": "facets/verona/calendar/20260310.jsonl", 565 - "score": -2.3, 565 + "score": -1.5, 566 566 "stream": null, 567 567 "text": "### Event: Board Presentation\n\n\n**Time Occurred:** 10:00 - 12:00\n**Participants:** <strong>Romeo</strong> Montague, Juliet Capulet, Friar Lawrence\n\nVerona Platform joint venture pitch\n" 568 568 }, ··· 578 578 "id": "20260310/agents/meetings.md:0", 579 579 "idx": 0, 580 580 "path": "20260310/agents/meetings.md", 581 - "score": -3.0, 581 + "score": -1.9, 582 582 "stream": null, 583 583 "text": "# Meetings\n\n- 08:30 Pre-Board Meeting Prep (<strong>Romeo</strong>, Juliet, Benvolio)\n" 584 584 } ··· 603 603 "id": "20260304/default/180000_300/agents/audio.md:0", 604 604 "idx": 0, 605 605 "path": "20260304/default/180000_300/agents/audio.md", 606 - "score": -2.9, 606 + "score": -1.9, 607 607 "stream": "default", 608 608 "text": "# Audio Summary\n\nEvening mixer at Denver Tech Summit. <strong>Romeo</strong> and Juliet had their first extended conversation about combining their API approaches. Mercutio tried to pull <strong>Romeo</strong> away to karaoke.\n" 609 609 }, ··· 619 619 "id": "facets/capulet/entities/20260304.jsonl:1", 620 620 "idx": 1, 621 621 "path": "facets/capulet/entities/20260304.jsonl", 622 - "score": -3.2, 622 + "score": -2.1, 623 623 "stream": null, 624 624 "text": "### Person: Tybalt Capulet\n\n\nConfronted <strong>Romeo</strong> at hackathon\n\n" 625 625 }, ··· 635 635 "id": "facets/montague/entities/20260304.jsonl:0", 636 636 "idx": 0, 637 637 "path": "facets/montague/entities/20260304.jsonl", 638 - "score": -3.0, 638 + "score": -1.9, 639 639 "stream": null, 640 640 "text": "### Person: <strong>Romeo</strong> Montague\n\n\nAttended Denver Tech Summit, met Juliet Capulet\n\n" 641 641 }, ··· 651 651 "id": "facets/capulet/events/20260304.jsonl:1", 652 652 "idx": 1, 653 653 "path": "facets/capulet/events/20260304.jsonl", 654 - "score": -3.2, 654 + "score": -2.1, 655 655 "stream": null, 656 656 "text": "### Social: Conference Mixer\n\n\n**Time Occurred:** 18:00 - 20:00\n**Participants:** Juliet Capulet, <strong>Romeo</strong> Montague\n\nNetworking event\n\nJuliet and <strong>Romeo</strong> exchanged Signal contacts\n" 657 657 }, ··· 667 667 "id": "facets/montague/events/20260304.jsonl:1", 668 668 "idx": 1, 669 669 "path": "facets/montague/events/20260304.jsonl", 670 - "score": -3.1, 670 + "score": -2.0, 671 671 "stream": null, 672 672 "text": "### Hackathon: Hackathon - API Bridge Challenge\n\n\n**Time Occurred:** 14:00 - 18:00\n**Participants:** <strong>Romeo</strong> Montague, Mercutio Escalus\n\nBuilt API bridge prototype\n\nTybalt confronted <strong>Romeo</strong>\n" 673 673 }
-20
tests/baselines/api/settings/generators.json
··· 64 64 }, 65 65 { 66 66 "app": null, 67 - "description": "Extracts patterns from segment data during onboarding observation", 68 - "disabled": true, 69 - "extract": null, 70 - "has_extraction": false, 71 - "key": "observation", 72 - "source": "system", 73 - "title": "Observation" 74 - }, 75 - { 76 - "app": null, 77 67 "description": "Extracts people, companies, projects, and tools from segment content", 78 68 "disabled": false, 79 69 "extract": null, ··· 91 81 "key": "speaker_attribution", 92 82 "source": "system", 93 83 "title": "Speaker Attribution" 94 - }, 95 - { 96 - "app": null, 97 - "description": "One-shot check-in after onboarding — spawns support agent chat", 98 - "disabled": true, 99 - "extract": null, 100 - "has_extraction": false, 101 - "key": "firstday_checkin", 102 - "source": "system", 103 - "title": "First-Day Check-In" 104 84 }, 105 85 { 106 86 "app": null,
-30
tests/baselines/api/settings/providers.json
··· 215 215 "tier": 3, 216 216 "type": "cogitate" 217 217 }, 218 - "talent.system.firstday_checkin": { 219 - "disabled": true, 220 - "group": "Think", 221 - "label": "First-Day Check-In", 222 - "schedule": "segment", 223 - "tier": 3, 224 - "type": "generate" 225 - }, 226 218 "talent.system.flow": { 227 219 "disabled": false, 228 220 "extract": true, ··· 299 291 "tier": 2, 300 292 "type": "cogitate" 301 293 }, 302 - "talent.system.observation": { 303 - "disabled": true, 304 - "group": "Think", 305 - "label": "Observation", 306 - "schedule": "segment", 307 - "tier": 3, 308 - "type": "generate" 309 - }, 310 - "talent.system.observation_review": { 311 - "disabled": true, 312 - "group": "Think", 313 - "label": "Observation Review", 314 - "tier": 2, 315 - "type": "cogitate" 316 - }, 317 294 "talent.system.occurrence": { 318 295 "disabled": false, 319 296 "group": "Think", 320 297 "label": "Occurrence Extraction", 321 298 "tier": 2, 322 299 "type": null 323 - }, 324 - "talent.system.onboarding": { 325 - "disabled": true, 326 - "group": "Think", 327 - "label": "Onboarding", 328 - "tier": 2, 329 - "type": "cogitate" 330 300 }, 331 301 "talent.system.partner": { 332 302 "disabled": false,
+1 -1509
tests/baselines/api/stats/stats.json
··· 308 308 "type": "generate" 309 309 } 310 310 }, 311 - "stats": { 312 - "agent_counts": { 313 - "activity": 2, 314 - "flow": 11, 315 - "meetings": 8 316 - }, 317 - "agent_counts_by_day": { 318 - "20240101": { 319 - "activity": 2, 320 - "meetings": 1 321 - }, 322 - "20260304": { 323 - "flow": 4 324 - }, 325 - "20260305": { 326 - "flow": 1, 327 - "meetings": 1 328 - }, 329 - "20260306": { 330 - "flow": 2, 331 - "meetings": 1 332 - }, 333 - "20260307": { 334 - "flow": 2, 335 - "meetings": 1 336 - }, 337 - "20260308": { 338 - "meetings": 1 339 - }, 340 - "20260309": { 341 - "flow": 2 342 - }, 343 - "20260310": { 344 - "meetings": 3 345 - } 346 - }, 347 - "agent_minutes": { 348 - "activity": 180.0, 349 - "flow": 1979.0, 350 - "meetings": 570.0 351 - }, 352 - "days": { 353 - "20240101": { 354 - "audio_duration": 44.0, 355 - "audio_segments": 6, 356 - "audio_sessions": 2, 357 - "day_bytes": 39028, 358 - "outputs_pending": 9, 359 - "outputs_processed": 2, 360 - "pending_segments": 0, 361 - "screen_duration": 0.0, 362 - "screen_frames": 0, 363 - "screen_sessions": 1 364 - }, 365 - "20240102": { 366 - "audio_duration": 29.0, 367 - "audio_segments": 3, 368 - "audio_sessions": 1, 369 - "day_bytes": 38591, 370 - "outputs_pending": 11, 371 - "outputs_processed": 1, 372 - "pending_segments": 0, 373 - "screen_duration": 23.1, 374 - "screen_frames": 3, 375 - "screen_sessions": 1 376 - }, 377 - "20250101": { 378 - "audio_duration": 0.0, 379 - "day_bytes": 68855, 380 - "outputs_pending": 12, 381 - "outputs_processed": 0, 382 - "pending_segments": 0, 383 - "screen_duration": 0.0 384 - }, 385 - "20250103": { 386 - "audio_duration": 0.0, 387 - "day_bytes": 3991, 388 - "outputs_pending": 12, 389 - "outputs_processed": 0, 390 - "pending_segments": 0, 391 - "screen_duration": 0.0 392 - }, 393 - "20250104": { 394 - "audio_duration": 0.0, 395 - "day_bytes": 12058, 396 - "outputs_pending": 12, 397 - "outputs_processed": 0, 398 - "pending_segments": 0, 399 - "screen_duration": 0.0 400 - }, 401 - "20250107": { 402 - "audio_duration": 0.0, 403 - "day_bytes": 4017, 404 - "outputs_pending": 12, 405 - "outputs_processed": 0, 406 - "pending_segments": 0, 407 - "screen_duration": 0.0 408 - }, 409 - "20250108": { 410 - "audio_duration": 0.0, 411 - "day_bytes": 12025, 412 - "outputs_pending": 12, 413 - "outputs_processed": 0, 414 - "pending_segments": 0, 415 - "screen_duration": 0.0 416 - }, 417 - "20250110": { 418 - "day_bytes": 15854, 419 - "outputs_pending": 11, 420 - "outputs_processed": 0, 421 - "pending_segments": 0, 422 - "percept_duration": 0.0, 423 - "transcript_duration": 0.0 424 - }, 425 - "20250124": { 426 - "audio_duration": 0.0, 427 - "day_bytes": 776, 428 - "outputs_pending": 12, 429 - "outputs_processed": 0, 430 - "pending_segments": 0, 431 - "screen_duration": 0.0 432 - }, 433 - "20260101": { 434 - "day_bytes": 24508, 435 - "outputs_pending": 11, 436 - "outputs_processed": 0, 437 - "pending_segments": 0, 438 - "percept_duration": 0.0, 439 - "transcript_duration": 1557.0, 440 - "transcript_segments": 38, 441 - "transcript_sessions": 8 442 - }, 443 - "20260130": { 444 - "audio_duration": 0.0, 445 - "day_bytes": 275, 446 - "outputs_pending": 12, 447 - "outputs_processed": 0, 448 - "pending_segments": 0, 449 - "screen_duration": 0.0 450 - }, 451 - "20260216": { 452 - "audio_duration": 0.0, 453 - "day_bytes": 397, 454 - "outputs_pending": 12, 455 - "outputs_processed": 0, 456 - "pending_segments": 0, 457 - "screen_duration": 0.0 458 - }, 459 - "20260217": { 460 - "audio_duration": 0.0, 461 - "day_bytes": 320, 462 - "outputs_pending": 12, 463 - "outputs_processed": 0, 464 - "pending_segments": 0, 465 - "screen_duration": 0.0 466 - }, 467 - "20260218": { 468 - "audio_duration": 0.0, 469 - "day_bytes": 120, 470 - "outputs_pending": 12, 471 - "outputs_processed": 0, 472 - "pending_segments": 0, 473 - "screen_duration": 0.0 474 - }, 475 - "20260219": { 476 - "audio_duration": 0.0, 477 - "day_bytes": 240, 478 - "outputs_pending": 12, 479 - "outputs_processed": 0, 480 - "pending_segments": 0, 481 - "screen_duration": 0.0 482 - }, 483 - "20260220": { 484 - "audio_duration": 0.0, 485 - "day_bytes": 0, 486 - "outputs_pending": 12, 487 - "outputs_processed": 0, 488 - "pending_segments": 0, 489 - "screen_duration": 0.0 490 - }, 491 - "20260221": { 492 - "audio_duration": 0.0, 493 - "day_bytes": 160, 494 - "outputs_pending": 12, 495 - "outputs_processed": 0, 496 - "pending_segments": 0, 497 - "screen_duration": 0.0 498 - }, 499 - "20260222": { 500 - "audio_duration": 0.0, 501 - "day_bytes": 80, 502 - "outputs_pending": 12, 503 - "outputs_processed": 0, 504 - "pending_segments": 0, 505 - "screen_duration": 0.0 506 - }, 507 - "20260223": { 508 - "audio_duration": 0.0, 509 - "day_bytes": 120, 510 - "outputs_pending": 12, 511 - "outputs_processed": 0, 512 - "pending_segments": 0, 513 - "screen_duration": 0.0 514 - }, 515 - "20260224": { 516 - "audio_duration": 0.0, 517 - "day_bytes": 160, 518 - "outputs_pending": 11, 519 - "outputs_processed": 0, 520 - "pending_segments": 0, 521 - "screen_duration": 0.0 522 - }, 523 - "20260225": { 524 - "audio_duration": 0.0, 525 - "day_bytes": 120, 526 - "outputs_pending": 11, 527 - "outputs_processed": 0, 528 - "pending_segments": 0, 529 - "screen_duration": 0.0 530 - }, 531 - "20260226": { 532 - "audio_duration": 0.0, 533 - "day_bytes": 80, 534 - "outputs_pending": 11, 535 - "outputs_processed": 0, 536 - "pending_segments": 0, 537 - "screen_duration": 0.0 538 - }, 539 - "20260227": { 540 - "audio_duration": 0.0, 541 - "day_bytes": 80, 542 - "outputs_pending": 11, 543 - "outputs_processed": 0, 544 - "pending_segments": 0, 545 - "screen_duration": 0.0 546 - }, 547 - "20260228": { 548 - "audio_duration": 0.0, 549 - "day_bytes": 120, 550 - "outputs_pending": 11, 551 - "outputs_processed": 0, 552 - "pending_segments": 0, 553 - "screen_duration": 0.0 554 - }, 555 - "20260301": { 556 - "audio_duration": 0.0, 557 - "day_bytes": 80, 558 - "outputs_pending": 11, 559 - "outputs_processed": 0, 560 - "pending_segments": 0, 561 - "screen_duration": 0.0 562 - }, 563 - "20260302": { 564 - "audio_duration": 0.0, 565 - "day_bytes": 637, 566 - "outputs_pending": 11, 567 - "outputs_processed": 0, 568 - "pending_segments": 0, 569 - "screen_duration": 0.0 570 - }, 571 - "20260303": { 572 - "audio_duration": 0.0, 573 - "day_bytes": 240, 574 - "outputs_pending": 11, 575 - "outputs_processed": 0, 576 - "pending_segments": 0, 577 - "screen_duration": 0.0 578 - }, 579 - "20260304": { 580 - "audio_duration": 250.0, 581 - "audio_segments": 15, 582 - "audio_sessions": 3, 583 - "day_bytes": 44511, 584 - "outputs_pending": 9, 585 - "outputs_processed": 2, 586 - "pending_segments": 0, 587 - "screen_duration": 0.0, 588 - "screen_frames": 0, 589 - "screen_sessions": 3 590 - }, 591 - "20260305": { 592 - "audio_duration": 175.0, 593 - "audio_segments": 12, 594 - "audio_sessions": 3, 595 - "day_bytes": 41560, 596 - "outputs_pending": 10, 597 - "outputs_processed": 1, 598 - "pending_segments": 0, 599 - "screen_duration": 0.0, 600 - "screen_frames": 0, 601 - "screen_sessions": 3 602 - }, 603 - "20260306": { 604 - "day_bytes": 62860, 605 - "outputs_pending": 9, 606 - "outputs_processed": 2, 607 - "pending_segments": 0, 608 - "percept_duration": 0.0, 609 - "percept_frames": 0, 610 - "percept_sessions": 4, 611 - "transcript_duration": 655.0, 612 - "transcript_segments": 48, 613 - "transcript_sessions": 4 614 - }, 615 - "20260307": { 616 - "audio_duration": 160.0, 617 - "audio_segments": 11, 618 - "audio_sessions": 2, 619 - "day_bytes": 38153, 620 - "outputs_pending": 10, 621 - "outputs_processed": 1, 622 - "pending_segments": 0, 623 - "screen_duration": 0.0 624 - }, 625 - "20260308": { 626 - "audio_duration": 140.0, 627 - "audio_segments": 9, 628 - "audio_sessions": 2, 629 - "day_bytes": 39213, 630 - "outputs_pending": 9, 631 - "outputs_processed": 2, 632 - "pending_segments": 0, 633 - "screen_duration": 0.0 634 - }, 635 - "20260309": { 636 - "audio_duration": 170.0, 637 - "audio_segments": 12, 638 - "audio_sessions": 3, 639 - "day_bytes": 41937, 640 - "outputs_pending": 10, 641 - "outputs_processed": 1, 642 - "pending_segments": 0, 643 - "screen_duration": 0.0, 644 - "screen_frames": 0, 645 - "screen_sessions": 3 646 - }, 647 - "20260310": { 648 - "day_bytes": 53726, 649 - "outputs_pending": 9, 650 - "outputs_processed": 2, 651 - "pending_segments": 0, 652 - "percept_duration": 0.0, 653 - "percept_frames": 0, 654 - "percept_sessions": 2, 655 - "transcript_duration": 275.0, 656 - "transcript_segments": 18, 657 - "transcript_sessions": 3 658 - }, 659 - "20260311": { 660 - "day_bytes": 483, 661 - "outputs_pending": 11, 662 - "outputs_processed": 0, 663 - "pending_segments": 0, 664 - "percept_duration": 0.0, 665 - "transcript_duration": 0.0 666 - }, 667 - "20260312": { 668 - "day_bytes": 520, 669 - "outputs_pending": 11, 670 - "outputs_processed": 0, 671 - "pending_segments": 0, 672 - "percept_duration": 0.0, 673 - "transcript_duration": 0.0 674 - }, 675 - "20260314": { 676 - "day_bytes": 160, 677 - "outputs_pending": 11, 678 - "outputs_processed": 0, 679 - "pending_segments": 0, 680 - "percept_duration": 0.0, 681 - "transcript_duration": 0.0 682 - }, 683 - "20260315": { 684 - "day_bytes": 483, 685 - "outputs_pending": 11, 686 - "outputs_processed": 0, 687 - "pending_segments": 0, 688 - "percept_duration": 0.0, 689 - "transcript_duration": 0.0 690 - }, 691 - "20260316": { 692 - "day_bytes": 160, 693 - "outputs_pending": 11, 694 - "outputs_processed": 0, 695 - "pending_segments": 0, 696 - "percept_duration": 0.0, 697 - "transcript_duration": 0.0 698 - }, 699 - "20260317": { 700 - "day_bytes": 403, 701 - "outputs_pending": 11, 702 - "outputs_processed": 0, 703 - "pending_segments": 0, 704 - "percept_duration": 0.0, 705 - "transcript_duration": 0.0 706 - }, 707 - "20260318": { 708 - "day_bytes": 360, 709 - "outputs_pending": 11, 710 - "outputs_processed": 0, 711 - "pending_segments": 0, 712 - "percept_duration": 0.0, 713 - "transcript_duration": 0.0 714 - }, 715 - "20260319": { 716 - "day_bytes": 120, 717 - "outputs_pending": 11, 718 - "outputs_processed": 0, 719 - "pending_segments": 0, 720 - "percept_duration": 0.0, 721 - "transcript_duration": 0.0 722 - }, 723 - "20260320": { 724 - "day_bytes": 350, 725 - "outputs_pending": 11, 726 - "outputs_processed": 0, 727 - "pending_segments": 0, 728 - "percept_duration": 0.0, 729 - "transcript_duration": 0.0 730 - }, 731 - "20260321": { 732 - "day_bytes": 660, 733 - "outputs_pending": 11, 734 - "outputs_processed": 0, 735 - "pending_segments": 0, 736 - "percept_duration": 0.0, 737 - "transcript_duration": 0.0 738 - }, 739 - "20260322": { 740 - "day_bytes": 746, 741 - "outputs_pending": 11, 742 - "outputs_processed": 0, 743 - "pending_segments": 0, 744 - "percept_duration": 0.0, 745 - "transcript_duration": 0.0 746 - }, 747 - "20260323": { 748 - "day_bytes": 0, 749 - "outputs_pending": 11, 750 - "outputs_processed": 0, 751 - "pending_segments": 0, 752 - "percept_duration": 0.0, 753 - "transcript_duration": 0.0 754 - }, 755 - "20260326": { 756 - "day_bytes": 792, 757 - "outputs_pending": 11, 758 - "outputs_processed": 0, 759 - "pending_segments": 0, 760 - "percept_duration": 0.0, 761 - "transcript_duration": 0.0 762 - }, 763 - "20260327": { 764 - "day_bytes": 3300, 765 - "outputs_pending": 11, 766 - "outputs_processed": 0, 767 - "pending_segments": 0, 768 - "percept_duration": 0.0, 769 - "transcript_duration": 0.0 770 - }, 771 - "20260331": { 772 - "day_bytes": 884, 773 - "outputs_pending": 11, 774 - "outputs_processed": 0, 775 - "pending_segments": 0, 776 - "percept_duration": 0.0, 777 - "transcript_duration": 0.0 778 - }, 779 - "20260402": { 780 - "day_bytes": 1201, 781 - "outputs_pending": 11, 782 - "outputs_processed": 0, 783 - "pending_segments": 0, 784 - "percept_duration": 0.0, 785 - "transcript_duration": 0.0 786 - }, 787 - "20260403": { 788 - "day_bytes": 1056, 789 - "outputs_pending": 11, 790 - "outputs_processed": 0, 791 - "pending_segments": 0, 792 - "percept_duration": 0.0, 793 - "transcript_duration": 0.0 794 - }, 795 - "20260404": { 796 - "day_bytes": 878, 797 - "outputs_pending": 5, 798 - "outputs_processed": 0, 799 - "pending_segments": 0, 800 - "percept_duration": 0.0, 801 - "transcript_duration": 0.0 802 - }, 803 - "20260405": { 804 - "day_bytes": 528, 805 - "outputs_pending": 5, 806 - "outputs_processed": 0, 807 - "pending_segments": 0, 808 - "percept_duration": 0.0, 809 - "transcript_duration": 0.0 810 - }, 811 - "20990101": { 812 - "audio_duration": 0.0, 813 - "day_bytes": 0, 814 - "outputs_pending": 12, 815 - "outputs_processed": 0, 816 - "pending_segments": 0, 817 - "screen_duration": 0.0 818 - } 819 - }, 820 - "facet_counts": { 821 - "capulet": 5, 822 - "montague": 8, 823 - "personal": 1, 824 - "verona": 5, 825 - "work": 2 826 - }, 827 - "facet_counts_by_day": { 828 - "20240101": { 829 - "personal": 1, 830 - "work": 2 831 - }, 832 - "20260304": { 833 - "capulet": 2, 834 - "montague": 2 835 - }, 836 - "20260305": { 837 - "montague": 1, 838 - "verona": 1 839 - }, 840 - "20260306": { 841 - "capulet": 1, 842 - "montague": 1, 843 - "verona": 1 844 - }, 845 - "20260307": { 846 - "capulet": 1, 847 - "montague": 2 848 - }, 849 - "20260308": { 850 - "verona": 1 851 - }, 852 - "20260309": { 853 - "montague": 1, 854 - "verona": 1 855 - }, 856 - "20260310": { 857 - "capulet": 1, 858 - "montague": 1, 859 - "verona": 1 860 - } 861 - }, 862 - "facet_minutes": { 863 - "capulet": 390.0, 864 - "montague": 810.0, 865 - "personal": 60.0, 866 - "verona": 1319.0, 867 - "work": 150.0 868 - }, 869 - "heatmap": [ 870 - [ 871 - 0.0, 872 - 0.0, 873 - 0.0, 874 - 0.0, 875 - 0.0, 876 - 0.0, 877 - 0.0, 878 - 0.0, 879 - 0.0, 880 - 150.0, 881 - 180.0, 882 - 120.0, 883 - 60.0, 884 - 60.0, 885 - 60.0, 886 - 60.0, 887 - 60.0, 888 - 60.0, 889 - 120.0, 890 - 60.0, 891 - 60.0, 892 - 0.0, 893 - 0.0, 894 - 0.0 895 - ], 896 - [ 897 - 0.0, 898 - 0.0, 899 - 0.0, 900 - 0.0, 901 - 0.0, 902 - 0.0, 903 - 0.0, 904 - 0.0, 905 - 0.0, 906 - 0.0, 907 - 180.0, 908 - 180.0, 909 - 0.0, 910 - 0.0, 911 - 0.0, 912 - 0.0, 913 - 0.0, 914 - 0.0, 915 - 0.0, 916 - 0.0, 917 - 0.0, 918 - 0.0, 919 - 0.0, 920 - 0.0 921 - ], 922 - [ 923 - 0.0, 924 - 0.0, 925 - 0.0, 926 - 0.0, 927 - 0.0, 928 - 0.0, 929 - 0.0, 930 - 0.0, 931 - 0.0, 932 - 120.0, 933 - 60.0, 934 - 60.0, 935 - 0.0, 936 - 0.0, 937 - 60.0, 938 - 60.0, 939 - 60.0, 940 - 60.0, 941 - 60.0, 942 - 60.0, 943 - 0.0, 944 - 0.0, 945 - 0.0, 946 - 0.0 947 - ], 948 - [ 949 - 0.0, 950 - 0.0, 951 - 0.0, 952 - 0.0, 953 - 0.0, 954 - 0.0, 955 - 0.0, 956 - 0.0, 957 - 0.0, 958 - 30.0, 959 - 0.0, 960 - 0.0, 961 - 0.0, 962 - 0.0, 963 - 0.0, 964 - 0.0, 965 - 0.0, 966 - 0.0, 967 - 0.0, 968 - 0.0, 969 - 0.0, 970 - 0.0, 971 - 60.0, 972 - 59.0 973 - ], 974 - [ 975 - 0.0, 976 - 0.0, 977 - 0.0, 978 - 0.0, 979 - 0.0, 980 - 0.0, 981 - 0.0, 982 - 0.0, 983 - 0.0, 984 - 30.0, 985 - 0.0, 986 - 60.0, 987 - 0.0, 988 - 0.0, 989 - 30.0, 990 - 60.0, 991 - 60.0, 992 - 60.0, 993 - 60.0, 994 - 30.0, 995 - 0.0, 996 - 0.0, 997 - 0.0, 998 - 0.0 999 - ], 1000 - [ 1001 - 0.0, 1002 - 0.0, 1003 - 0.0, 1004 - 0.0, 1005 - 0.0, 1006 - 0.0, 1007 - 0.0, 1008 - 0.0, 1009 - 0.0, 1010 - 0.0, 1011 - 60.0, 1012 - 0.0, 1013 - 0.0, 1014 - 0.0, 1015 - 0.0, 1016 - 60.0, 1017 - 0.0, 1018 - 0.0, 1019 - 0.0, 1020 - 0.0, 1021 - 0.0, 1022 - 0.0, 1023 - 0.0, 1024 - 0.0 1025 - ], 1026 - [ 1027 - 0.0, 1028 - 0.0, 1029 - 0.0, 1030 - 0.0, 1031 - 0.0, 1032 - 0.0, 1033 - 0.0, 1034 - 0.0, 1035 - 0.0, 1036 - 0.0, 1037 - 60.0, 1038 - 0.0, 1039 - 0.0, 1040 - 0.0, 1041 - 0.0, 1042 - 0.0, 1043 - 0.0, 1044 - 0.0, 1045 - 0.0, 1046 - 0.0, 1047 - 0.0, 1048 - 0.0, 1049 - 0.0, 1050 - 0.0 1051 - ] 1052 - ], 1053 - "token_totals_by_model": { 1054 - "claude-sonnet-4-5": { 1055 - "cached_tokens": 7000, 1056 - "input_tokens": 29400, 1057 - "output_tokens": 10500, 1058 - "reasoning_tokens": 1400, 1059 - "total_tokens": 39900 1060 - }, 1061 - "claude-sonnet-4-5-20250929": { 1062 - "input_tokens": 24000, 1063 - "output_tokens": 7200, 1064 - "total_tokens": 31200 1065 - }, 1066 - "clean-format-test": { 1067 - "input_tokens": 100, 1068 - "output_tokens": 50, 1069 - "total_tokens": 150 1070 - }, 1071 - "gemini-2.5-flash": { 1072 - "cached_tokens": 7250, 1073 - "input_tokens": 60161, 1074 - "output_tokens": 22106, 1075 - "reasoning_tokens": 30081, 1076 - "total_tokens": 110298 1077 - }, 1078 - "gemini-2.5-flash-lite": { 1079 - "cached_tokens": 0, 1080 - "input_tokens": 2198, 1081 - "output_tokens": 258, 1082 - "reasoning_tokens": 0, 1083 - "total_tokens": 2456 1084 - }, 1085 - "gemini-3-flash-preview": { 1086 - "input_tokens": 1944, 1087 - "output_tokens": 486, 1088 - "reasoning_tokens": 5098, 1089 - "total_tokens": 13556 1090 - }, 1091 - "gpt-5": { 1092 - "cached_tokens": 200, 1093 - "input_tokens": 9400, 1094 - "output_tokens": 3270, 1095 - "reasoning_tokens": 600, 1096 - "requests": 1, 1097 - "total_tokens": 12670 1098 - }, 1099 - "models/gemini-2.5-flash": { 1100 - "cached_tokens": 0, 1101 - "input_tokens": 1143, 1102 - "output_tokens": 373, 1103 - "reasoning_tokens": 3267, 1104 - "total_tokens": 4783 1105 - }, 1106 - "models/gemini-2.5-flash-lite": { 1107 - "cached_tokens": 0, 1108 - "input_tokens": 60, 1109 - "output_tokens": 5, 1110 - "reasoning_tokens": 0, 1111 - "total_tokens": 65 1112 - } 1113 - }, 1114 - "token_usage_by_day": { 1115 - "20250823": { 1116 - "claude-sonnet-4-5-20250929": { 1117 - "input_tokens": 24000, 1118 - "output_tokens": 7200, 1119 - "total_tokens": 31200 1120 - }, 1121 - "gemini-2.5-flash": { 1122 - "cached_tokens": 3450, 1123 - "input_tokens": 21850, 1124 - "output_tokens": 7256, 1125 - "reasoning_tokens": 2139, 1126 - "total_tokens": 29345 1127 - }, 1128 - "gemini-2.5-flash-lite": { 1129 - "cached_tokens": 0, 1130 - "input_tokens": 312, 1131 - "output_tokens": 81, 1132 - "reasoning_tokens": 0, 1133 - "total_tokens": 393 1134 - }, 1135 - "gpt-5": { 1136 - "input_tokens": 8400, 1137 - "output_tokens": 2770, 1138 - "reasoning_tokens": 500, 1139 - "total_tokens": 11170 1140 - }, 1141 - "models/gemini-2.5-flash": { 1142 - "cached_tokens": 0, 1143 - "input_tokens": 1143, 1144 - "output_tokens": 373, 1145 - "reasoning_tokens": 3267, 1146 - "total_tokens": 4783 1147 - }, 1148 - "models/gemini-2.5-flash-lite": { 1149 - "cached_tokens": 0, 1150 - "input_tokens": 60, 1151 - "output_tokens": 5, 1152 - "reasoning_tokens": 0, 1153 - "total_tokens": 65 1154 - } 1155 - }, 1156 - "20250824": { 1157 - "gemini-2.5-flash": { 1158 - "cached_tokens": 0, 1159 - "input_tokens": 1454, 1160 - "output_tokens": 679, 1161 - "reasoning_tokens": 528, 1162 - "total_tokens": 2661 1163 - }, 1164 - "gemini-2.5-flash-lite": { 1165 - "cached_tokens": 0, 1166 - "input_tokens": 12, 1167 - "output_tokens": 1, 1168 - "reasoning_tokens": 0, 1169 - "total_tokens": 13 1170 - } 1171 - }, 1172 - "20250825": { 1173 - "gemini-2.5-flash": { 1174 - "cached_tokens": 0, 1175 - "input_tokens": 200, 1176 - "output_tokens": 100, 1177 - "reasoning_tokens": 0, 1178 - "total_tokens": 300 1179 - }, 1180 - "gemini-2.5-flash-lite": { 1181 - "cached_tokens": 0, 1182 - "input_tokens": 0, 1183 - "output_tokens": 0, 1184 - "reasoning_tokens": 0, 1185 - "total_tokens": 0 1186 - } 1187 - }, 1188 - "20250826": { 1189 - "gemini-2.5-flash": { 1190 - "cached_tokens": 0, 1191 - "input_tokens": 500, 1192 - "output_tokens": 250, 1193 - "reasoning_tokens": 0, 1194 - "total_tokens": 750 1195 - }, 1196 - "gemini-2.5-flash-lite": { 1197 - "cached_tokens": 0, 1198 - "input_tokens": 0, 1199 - "output_tokens": 0, 1200 - "reasoning_tokens": 0, 1201 - "total_tokens": 0 1202 - } 1203 - }, 1204 - "20250827": { 1205 - "gemini-2.5-flash": { 1206 - "cached_tokens": 0, 1207 - "input_tokens": 1130, 1208 - "output_tokens": 415, 1209 - "reasoning_tokens": 3246, 1210 - "total_tokens": 4791 1211 - }, 1212 - "gemini-2.5-flash-lite": { 1213 - "cached_tokens": 0, 1214 - "input_tokens": 60, 1215 - "output_tokens": 5, 1216 - "reasoning_tokens": 0, 1217 - "total_tokens": 65 1218 - } 1219 - }, 1220 - "20250829": { 1221 - "gemini-2.5-flash": { 1222 - "cached_tokens": 0, 1223 - "input_tokens": 200, 1224 - "output_tokens": 100, 1225 - "reasoning_tokens": 0, 1226 - "total_tokens": 300 1227 - }, 1228 - "gemini-2.5-flash-lite": { 1229 - "cached_tokens": 0, 1230 - "input_tokens": 0, 1231 - "output_tokens": 0, 1232 - "reasoning_tokens": 0, 1233 - "total_tokens": 0 1234 - } 1235 - }, 1236 - "20250905": { 1237 - "gemini-2.5-flash": { 1238 - "cached_tokens": 0, 1239 - "input_tokens": 1270, 1240 - "output_tokens": 591, 1241 - "reasoning_tokens": 3355, 1242 - "total_tokens": 5216 1243 - }, 1244 - "gemini-2.5-flash-lite": { 1245 - "cached_tokens": 0, 1246 - "input_tokens": 60, 1247 - "output_tokens": 5, 1248 - "reasoning_tokens": 0, 1249 - "total_tokens": 65 1250 - } 1251 - }, 1252 - "20250906": { 1253 - "gemini-2.5-flash": { 1254 - "cached_tokens": 0, 1255 - "input_tokens": 674, 1256 - "output_tokens": 328, 1257 - "reasoning_tokens": 709, 1258 - "total_tokens": 1711 1259 - }, 1260 - "gemini-2.5-flash-lite": { 1261 - "cached_tokens": 0, 1262 - "input_tokens": 12, 1263 - "output_tokens": 1, 1264 - "reasoning_tokens": 0, 1265 - "total_tokens": 13 1266 - } 1267 - }, 1268 - "20250909": { 1269 - "gemini-2.5-flash": { 1270 - "cached_tokens": 0, 1271 - "input_tokens": 1518, 1272 - "output_tokens": 642, 1273 - "reasoning_tokens": 5004, 1274 - "total_tokens": 7164 1275 - }, 1276 - "gemini-2.5-flash-lite": { 1277 - "cached_tokens": 0, 1278 - "input_tokens": 84, 1279 - "output_tokens": 7, 1280 - "reasoning_tokens": 0, 1281 - "total_tokens": 91 1282 - } 1283 - }, 1284 - "20250910": { 1285 - "gemini-2.5-flash": { 1286 - "cached_tokens": 0, 1287 - "input_tokens": 300, 1288 - "output_tokens": 150, 1289 - "reasoning_tokens": 0, 1290 - "total_tokens": 450 1291 - }, 1292 - "gemini-2.5-flash-lite": { 1293 - "cached_tokens": 0, 1294 - "input_tokens": 0, 1295 - "output_tokens": 0, 1296 - "reasoning_tokens": 0, 1297 - "total_tokens": 0 1298 - } 1299 - }, 1300 - "20250914": { 1301 - "gemini-2.5-flash": { 1302 - "cached_tokens": 0, 1303 - "input_tokens": 1348, 1304 - "output_tokens": 654, 1305 - "reasoning_tokens": 1365, 1306 - "total_tokens": 3367 1307 - }, 1308 - "gemini-2.5-flash-lite": { 1309 - "cached_tokens": 0, 1310 - "input_tokens": 24, 1311 - "output_tokens": 2, 1312 - "reasoning_tokens": 0, 1313 - "total_tokens": 26 1314 - } 1315 - }, 1316 - "20250915": { 1317 - "gemini-2.5-flash": { 1318 - "cached_tokens": 0, 1319 - "input_tokens": 474, 1320 - "output_tokens": 218, 1321 - "reasoning_tokens": 662, 1322 - "total_tokens": 1354 1323 - }, 1324 - "gemini-2.5-flash-lite": { 1325 - "cached_tokens": 0, 1326 - "input_tokens": 12, 1327 - "output_tokens": 1, 1328 - "reasoning_tokens": 0, 1329 - "total_tokens": 13 1330 - } 1331 - }, 1332 - "20250916": { 1333 - "gemini-2.5-flash": { 1334 - "input_tokens": 348, 1335 - "output_tokens": 153, 1336 - "reasoning_tokens": 1307, 1337 - "total_tokens": 1808 1338 - }, 1339 - "gemini-2.5-flash-lite": { 1340 - "input_tokens": 24, 1341 - "output_tokens": 2, 1342 - "total_tokens": 26 1343 - } 1344 - }, 1345 - "20250917": { 1346 - "gemini-2.5-flash": { 1347 - "input_tokens": 174, 1348 - "output_tokens": 80, 1349 - "reasoning_tokens": 657, 1350 - "total_tokens": 911 1351 - }, 1352 - "gemini-2.5-flash-lite": { 1353 - "input_tokens": 12, 1354 - "output_tokens": 1, 1355 - "total_tokens": 13 1356 - } 1357 - }, 1358 - "20250919": { 1359 - "gemini-2.5-flash": { 1360 - "cached_tokens": 0, 1361 - "input_tokens": 200, 1362 - "output_tokens": 100, 1363 - "reasoning_tokens": 0, 1364 - "total_tokens": 300 1365 - }, 1366 - "gemini-2.5-flash-lite": { 1367 - "cached_tokens": 0, 1368 - "input_tokens": 0, 1369 - "output_tokens": 0, 1370 - "reasoning_tokens": 0, 1371 - "total_tokens": 0 1372 - } 1373 - }, 1374 - "20250920": { 1375 - "gemini-2.5-flash": { 1376 - "cached_tokens": 0, 1377 - "input_tokens": 100, 1378 - "output_tokens": 50, 1379 - "reasoning_tokens": 0, 1380 - "total_tokens": 150 1381 - }, 1382 - "gemini-2.5-flash-lite": { 1383 - "cached_tokens": 0, 1384 - "input_tokens": 0, 1385 - "output_tokens": 0, 1386 - "reasoning_tokens": 0, 1387 - "total_tokens": 0 1388 - } 1389 - }, 1390 - "20250921": { 1391 - "gemini-2.5-flash": { 1392 - "cached_tokens": 0, 1393 - "input_tokens": 800, 1394 - "output_tokens": 400, 1395 - "reasoning_tokens": 0, 1396 - "total_tokens": 1200 1397 - }, 1398 - "gemini-2.5-flash-lite": { 1399 - "cached_tokens": 0, 1400 - "input_tokens": 0, 1401 - "output_tokens": 0, 1402 - "reasoning_tokens": 0, 1403 - "total_tokens": 0 1404 - } 1405 - }, 1406 - "20250926": { 1407 - "gemini-2.5-flash": { 1408 - "input_tokens": 174, 1409 - "output_tokens": 79, 1410 - "reasoning_tokens": 648, 1411 - "total_tokens": 901 1412 - }, 1413 - "gemini-2.5-flash-lite": { 1414 - "input_tokens": 12, 1415 - "output_tokens": 1, 1416 - "total_tokens": 13 1417 - } 1418 - }, 1419 - "20250928": { 1420 - "gemini-2.5-flash": { 1421 - "cached_tokens": 0, 1422 - "input_tokens": 200, 1423 - "output_tokens": 100, 1424 - "reasoning_tokens": 0, 1425 - "total_tokens": 300 1426 - }, 1427 - "gemini-2.5-flash-lite": { 1428 - "cached_tokens": 0, 1429 - "input_tokens": 0, 1430 - "output_tokens": 0, 1431 - "reasoning_tokens": 0, 1432 - "total_tokens": 0 1433 - } 1434 - }, 1435 - "20251004": { 1436 - "gemini-2.5-flash": { 1437 - "cached_tokens": 0, 1438 - "input_tokens": 1000, 1439 - "output_tokens": 500, 1440 - "reasoning_tokens": 0, 1441 - "total_tokens": 1500 1442 - }, 1443 - "gemini-2.5-flash-lite": { 1444 - "cached_tokens": 0, 1445 - "input_tokens": 0, 1446 - "output_tokens": 0, 1447 - "reasoning_tokens": 0, 1448 - "total_tokens": 0 1449 - } 1450 - }, 1451 - "20251005": { 1452 - "gemini-2.5-flash": { 1453 - "cached_tokens": 0, 1454 - "input_tokens": 1274, 1455 - "output_tokens": 636, 1456 - "reasoning_tokens": 559, 1457 - "total_tokens": 2469 1458 - }, 1459 - "gemini-2.5-flash-lite": { 1460 - "cached_tokens": 0, 1461 - "input_tokens": 12, 1462 - "output_tokens": 1, 1463 - "reasoning_tokens": 0, 1464 - "total_tokens": 13 1465 - } 1466 - }, 1467 - "20251007": { 1468 - "gemini-2.5-flash": { 1469 - "input_tokens": 174, 1470 - "output_tokens": 79, 1471 - "reasoning_tokens": 636, 1472 - "total_tokens": 889 1473 - }, 1474 - "gemini-2.5-flash-lite": { 1475 - "input_tokens": 12, 1476 - "output_tokens": 1, 1477 - "total_tokens": 13 1478 - } 1479 - }, 1480 - "20251011": { 1481 - "gemini-2.5-flash": { 1482 - "cached_tokens": 0, 1483 - "input_tokens": 2685, 1484 - "output_tokens": 1137, 1485 - "reasoning_tokens": 4666, 1486 - "total_tokens": 8488 1487 - }, 1488 - "gemini-2.5-flash-lite": { 1489 - "cached_tokens": 0, 1490 - "input_tokens": 70, 1491 - "output_tokens": 7, 1492 - "reasoning_tokens": 0, 1493 - "total_tokens": 77 1494 - } 1495 - }, 1496 - "20251012": { 1497 - "gemini-2.5-flash": { 1498 - "cached_tokens": 300, 1499 - "input_tokens": 2144, 1500 - "output_tokens": 824, 1501 - "reasoning_tokens": 553, 1502 - "total_tokens": 3371 1503 - }, 1504 - "gemini-2.5-flash-lite": { 1505 - "input_tokens": 5, 1506 - "output_tokens": 1, 1507 - "total_tokens": 6 1508 - }, 1509 - "gpt-5": { 1510 - "cached_tokens": 200, 1511 - "input_tokens": 1000, 1512 - "output_tokens": 500, 1513 - "reasoning_tokens": 100, 1514 - "requests": 1, 1515 - "total_tokens": 1500 1516 - } 1517 - }, 1518 - "20251013": { 1519 - "gemini-2.5-flash": { 1520 - "input_tokens": 296, 1521 - "output_tokens": 101, 1522 - "reasoning_tokens": 948, 1523 - "total_tokens": 1345 1524 - }, 1525 - "gemini-2.5-flash-lite": { 1526 - "input_tokens": 5, 1527 - "output_tokens": 1, 1528 - "total_tokens": 6 1529 - } 1530 - }, 1531 - "20251015": { 1532 - "gemini-2.5-flash": { 1533 - "input_tokens": 830, 1534 - "output_tokens": 260, 1535 - "reasoning_tokens": 2691, 1536 - "total_tokens": 3781 1537 - }, 1538 - "gemini-2.5-flash-lite": { 1539 - "input_tokens": 34, 1540 - "output_tokens": 4, 1541 - "total_tokens": 38 1542 - } 1543 - }, 1544 - "20251016": { 1545 - "clean-format-test": { 1546 - "input_tokens": 100, 1547 - "output_tokens": 50, 1548 - "total_tokens": 150 1549 - } 1550 - }, 1551 - "20251025": { 1552 - "gemini-2.5-flash": { 1553 - "input_tokens": 1344, 1554 - "output_tokens": 624, 1555 - "reasoning_tokens": 408, 1556 - "total_tokens": 2376 1557 - }, 1558 - "gemini-2.5-flash-lite": { 1559 - "input_tokens": 5, 1560 - "output_tokens": 1, 1561 - "total_tokens": 6 1562 - } 1563 - }, 1564 - "20260211": { 1565 - "gemini-2.5-flash-lite": { 1566 - "input_tokens": 212, 1567 - "output_tokens": 20, 1568 - "total_tokens": 232 1569 - }, 1570 - "gemini-3-flash-preview": { 1571 - "input_tokens": 288, 1572 - "output_tokens": 72, 1573 - "total_tokens": 2021 1574 - } 1575 - }, 1576 - "20260214": { 1577 - "gemini-2.5-flash-lite": { 1578 - "input_tokens": 106, 1579 - "output_tokens": 10, 1580 - "total_tokens": 116 1581 - }, 1582 - "gemini-3-flash-preview": { 1583 - "input_tokens": 144, 1584 - "output_tokens": 36, 1585 - "total_tokens": 979 1586 - } 1587 - }, 1588 - "20260215": { 1589 - "gemini-2.5-flash-lite": { 1590 - "input_tokens": 106, 1591 - "output_tokens": 10, 1592 - "total_tokens": 116 1593 - }, 1594 - "gemini-3-flash-preview": { 1595 - "input_tokens": 144, 1596 - "output_tokens": 36, 1597 - "total_tokens": 977 1598 - } 1599 - }, 1600 - "20260216": { 1601 - "gemini-2.5-flash-lite": { 1602 - "input_tokens": 53, 1603 - "output_tokens": 5, 1604 - "total_tokens": 58 1605 - }, 1606 - "gemini-3-flash-preview": { 1607 - "input_tokens": 72, 1608 - "output_tokens": 18, 1609 - "total_tokens": 494 1610 - } 1611 - }, 1612 - "20260217": { 1613 - "gemini-2.5-flash-lite": { 1614 - "input_tokens": 265, 1615 - "output_tokens": 25, 1616 - "total_tokens": 290 1617 - }, 1618 - "gemini-3-flash-preview": { 1619 - "input_tokens": 360, 1620 - "output_tokens": 90, 1621 - "total_tokens": 2426 1622 - } 1623 - }, 1624 - "20260222": { 1625 - "gemini-2.5-flash-lite": { 1626 - "input_tokens": 53, 1627 - "output_tokens": 5, 1628 - "total_tokens": 58 1629 - }, 1630 - "gemini-3-flash-preview": { 1631 - "input_tokens": 72, 1632 - "output_tokens": 18, 1633 - "total_tokens": 481 1634 - } 1635 - }, 1636 - "20260305": { 1637 - "claude-sonnet-4-5": { 1638 - "cached_tokens": 1000, 1639 - "input_tokens": 4200, 1640 - "output_tokens": 1500, 1641 - "reasoning_tokens": 200, 1642 - "total_tokens": 5700 1643 - }, 1644 - "gemini-2.5-flash": { 1645 - "cached_tokens": 500, 1646 - "input_tokens": 2500, 1647 - "output_tokens": 800, 1648 - "reasoning_tokens": 0, 1649 - "total_tokens": 3300 1650 - } 1651 - }, 1652 - "20260306": { 1653 - "claude-sonnet-4-5": { 1654 - "cached_tokens": 1000, 1655 - "input_tokens": 4200, 1656 - "output_tokens": 1500, 1657 - "reasoning_tokens": 200, 1658 - "total_tokens": 5700 1659 - }, 1660 - "gemini-2.5-flash": { 1661 - "cached_tokens": 500, 1662 - "input_tokens": 2500, 1663 - "output_tokens": 800, 1664 - "reasoning_tokens": 0, 1665 - "total_tokens": 3300 1666 - } 1667 - }, 1668 - "20260307": { 1669 - "claude-sonnet-4-5": { 1670 - "cached_tokens": 1000, 1671 - "input_tokens": 4200, 1672 - "output_tokens": 1500, 1673 - "reasoning_tokens": 200, 1674 - "total_tokens": 5700 1675 - }, 1676 - "gemini-2.5-flash": { 1677 - "cached_tokens": 500, 1678 - "input_tokens": 2500, 1679 - "output_tokens": 800, 1680 - "reasoning_tokens": 0, 1681 - "total_tokens": 3300 1682 - } 1683 - }, 1684 - "20260308": { 1685 - "claude-sonnet-4-5": { 1686 - "cached_tokens": 1000, 1687 - "input_tokens": 4200, 1688 - "output_tokens": 1500, 1689 - "reasoning_tokens": 200, 1690 - "total_tokens": 5700 1691 - }, 1692 - "gemini-2.5-flash": { 1693 - "cached_tokens": 500, 1694 - "input_tokens": 2500, 1695 - "output_tokens": 800, 1696 - "reasoning_tokens": 0, 1697 - "total_tokens": 3300 1698 - } 1699 - }, 1700 - "20260309": { 1701 - "claude-sonnet-4-5": { 1702 - "cached_tokens": 1000, 1703 - "input_tokens": 4200, 1704 - "output_tokens": 1500, 1705 - "reasoning_tokens": 200, 1706 - "total_tokens": 5700 1707 - }, 1708 - "gemini-2.5-flash": { 1709 - "cached_tokens": 500, 1710 - "input_tokens": 2500, 1711 - "output_tokens": 800, 1712 - "reasoning_tokens": 0, 1713 - "total_tokens": 3300 1714 - } 1715 - }, 1716 - "20260310": { 1717 - "claude-sonnet-4-5": { 1718 - "cached_tokens": 1000, 1719 - "input_tokens": 4200, 1720 - "output_tokens": 1500, 1721 - "reasoning_tokens": 200, 1722 - "total_tokens": 5700 1723 - }, 1724 - "gemini-2.5-flash": { 1725 - "cached_tokens": 500, 1726 - "input_tokens": 2500, 1727 - "output_tokens": 800, 1728 - "reasoning_tokens": 0, 1729 - "total_tokens": 3300 1730 - } 1731 - }, 1732 - "20260311": { 1733 - "claude-sonnet-4-5": { 1734 - "cached_tokens": 1000, 1735 - "input_tokens": 4200, 1736 - "output_tokens": 1500, 1737 - "reasoning_tokens": 200, 1738 - "total_tokens": 5700 1739 - }, 1740 - "gemini-2.5-flash": { 1741 - "cached_tokens": 500, 1742 - "input_tokens": 2500, 1743 - "output_tokens": 800, 1744 - "reasoning_tokens": 0, 1745 - "total_tokens": 3300 1746 - } 1747 - }, 1748 - "20260315": { 1749 - "gemini-2.5-flash-lite": { 1750 - "input_tokens": 318, 1751 - "output_tokens": 30, 1752 - "total_tokens": 348 1753 - }, 1754 - "gemini-3-flash-preview": { 1755 - "input_tokens": 432, 1756 - "output_tokens": 108, 1757 - "reasoning_tokens": 2568, 1758 - "total_tokens": 3108 1759 - } 1760 - }, 1761 - "20260316": { 1762 - "gemini-2.5-flash-lite": { 1763 - "input_tokens": 159, 1764 - "output_tokens": 15, 1765 - "total_tokens": 174 1766 - }, 1767 - "gemini-3-flash-preview": { 1768 - "input_tokens": 216, 1769 - "output_tokens": 54, 1770 - "reasoning_tokens": 1267, 1771 - "total_tokens": 1537 1772 - } 1773 - }, 1774 - "20260318": { 1775 - "gemini-2.5-flash-lite": { 1776 - "input_tokens": 106, 1777 - "output_tokens": 10, 1778 - "total_tokens": 116 1779 - }, 1780 - "gemini-3-flash-preview": { 1781 - "input_tokens": 144, 1782 - "output_tokens": 36, 1783 - "reasoning_tokens": 826, 1784 - "total_tokens": 1006 1785 - } 1786 - }, 1787 - "20260319": { 1788 - "gemini-2.5-flash-lite": { 1789 - "input_tokens": 53, 1790 - "output_tokens": 5, 1791 - "total_tokens": 58 1792 - }, 1793 - "gemini-3-flash-preview": { 1794 - "input_tokens": 72, 1795 - "output_tokens": 18, 1796 - "reasoning_tokens": 437, 1797 - "total_tokens": 527 1798 - } 1799 - } 1800 - }, 1801 - "total_percept_duration": 0.0, 1802 - "total_transcript_duration": 2487.0, 1803 - "totals": { 1804 - "audio_duration": 968.0, 1805 - "audio_segments": 68, 1806 - "audio_sessions": 16, 1807 - "day_bytes": 557976, 1808 - "outputs_pending": 585, 1809 - "outputs_processed": 14, 1810 - "pending_segments": 0, 1811 - "percept_frames": 0, 1812 - "percept_sessions": 6, 1813 - "screen_duration": 23.1, 1814 - "screen_frames": 3, 1815 - "screen_sessions": 11, 1816 - "transcript_segments": 104, 1817 - "transcript_sessions": 15 1818 - } 1819 - } 311 + "stats": {} 1820 312 }
+1 -5
tests/baselines/api/tokens/stats-month.json
··· 5 5 "20260307": 0.038015, 6 6 "20260308": 0.038015, 7 7 "20260309": 0.038015, 8 - "20260310": 0.038015, 9 - "20260315": 0.011038, 10 - "20260316": 0.001342, 11 - "20260318": 0.002673, 12 - "20260319": 0.001408 8 + "20260310": 0.038015 13 9 }
+2 -2
tests/baselines/api/transcripts/segment-detail.json
··· 138 138 "screen": 0 139 139 }, 140 140 "segment_key": "090000_300", 141 - "warnings": 0, 142 - "video_files": {} 141 + "video_files": {}, 142 + "warnings": 0 143 143 }
-147
tests/test_awareness.py
··· 110 110 assert entries[0]["detail"] == "meeting detected" 111 111 112 112 113 - class TestOnboarding: 114 - def test_get_onboarding_empty(self): 115 - from think.awareness import get_onboarding 116 - 117 - assert get_onboarding() == {} 118 - 119 - def test_start_onboarding_path_a(self): 120 - from think.awareness import get_onboarding, start_onboarding 121 - 122 - state = start_onboarding("a") 123 - 124 - assert state["path"] == "a" 125 - assert state["status"] == "observing" 126 - assert state["observation_count"] == 0 127 - assert state["nudges_sent"] == 0 128 - assert "started" in state 129 - 130 - # Verify persisted 131 - assert get_onboarding()["status"] == "observing" 132 - 133 - def test_start_onboarding_path_b(self): 134 - from think.awareness import start_onboarding 135 - 136 - state = start_onboarding("b") 137 - assert state["path"] == "b" 138 - assert state["status"] == "interviewing" 139 - 140 - def test_skip_onboarding(self): 141 - from think.awareness import get_onboarding, skip_onboarding 142 - 143 - skip_onboarding() 144 - assert get_onboarding()["status"] == "skipped" 145 - 146 - def test_complete_onboarding(self): 147 - from think.awareness import complete_onboarding, start_onboarding 148 - 149 - start_onboarding("a") 150 - complete_onboarding() 151 - 152 - from think.awareness import get_onboarding 153 - 154 - state = get_onboarding() 155 - assert state["status"] == "complete" 156 - assert state["path"] == "a" # Preserved from start 157 - 158 - def test_start_onboarding_writes_log(self): 159 - from think.awareness import _today, read_log, start_onboarding 160 - 161 - start_onboarding("a") 162 - 163 - entries = read_log(_today()) 164 - assert len(entries) == 1 165 - assert entries[0]["kind"] == "state" 166 - assert entries[0]["key"] == "onboarding.started" 167 - assert entries[0]["data"]["path"] == "a" 168 - 169 - def test_skip_writes_log(self): 170 - from think.awareness import _today, read_log, skip_onboarding 171 - 172 - skip_onboarding() 173 - 174 - entries = read_log(_today()) 175 - assert entries[0]["key"] == "onboarding.skipped" 176 - 177 - def test_complete_writes_log(self): 178 - from think.awareness import _today, complete_onboarding, read_log 179 - 180 - complete_onboarding() 181 - 182 - entries = read_log(_today()) 183 - assert entries[0]["key"] == "onboarding.complete" 184 - 185 - 186 113 class TestAwarenessCLI: 187 114 def test_status_empty(self): 188 115 from typer.testing import CliRunner ··· 217 144 assert result.exit_code == 0 218 145 assert "observing" in result.output 219 146 220 - def test_onboarding_read_empty(self): 221 - from typer.testing import CliRunner 222 - 223 - from apps.awareness.call import app 224 - 225 - result = CliRunner().invoke(app, ["onboarding"]) 226 - assert result.exit_code == 0 227 - assert "No onboarding state" in result.output 228 - 229 - def test_onboarding_set_path_a(self): 230 - from typer.testing import CliRunner 231 - 232 - from apps.awareness.call import app 233 - 234 - result = CliRunner().invoke(app, ["onboarding", "--path", "a"]) 235 - assert result.exit_code == 0 236 - data = json.loads(result.output) 237 - assert data["path"] == "a" 238 - assert data["status"] == "observing" 239 - 240 - def test_onboarding_set_path_b(self): 241 - from typer.testing import CliRunner 242 - 243 - from apps.awareness.call import app 244 - 245 - result = CliRunner().invoke(app, ["onboarding", "--path", "b"]) 246 - assert result.exit_code == 0 247 - data = json.loads(result.output) 248 - assert data["path"] == "b" 249 - assert data["status"] == "interviewing" 250 - 251 - def test_onboarding_skip(self): 252 - from typer.testing import CliRunner 253 - 254 - from apps.awareness.call import app 255 - 256 - result = CliRunner().invoke(app, ["onboarding", "--skip"]) 257 - assert result.exit_code == 0 258 - data = json.loads(result.output) 259 - assert data["status"] == "skipped" 260 - 261 - def test_onboarding_complete(self): 262 - from typer.testing import CliRunner 263 - 264 - from apps.awareness.call import app 265 - 266 - result = CliRunner().invoke(app, ["onboarding", "--complete"]) 267 - assert result.exit_code == 0 268 - data = json.loads(result.output) 269 - assert data["status"] == "complete" 270 - 271 - def test_onboarding_invalid_path(self): 272 - from typer.testing import CliRunner 273 - 274 - from apps.awareness.call import app 275 - 276 - result = CliRunner().invoke(app, ["onboarding", "--path", "c"]) 277 - assert result.exit_code == 1 278 - 279 147 def test_log_cmd(self): 280 148 from typer.testing import CliRunner 281 149 ··· 316 184 state = get_current() 317 185 assert state["journal"]["first_daily_ready"] is True 318 186 assert state["journal"]["first_daily_ready_at"] == "20260308T14:00:00" 319 - 320 - def test_first_daily_ready_preserves_onboarding(self): 321 - from think.awareness import get_current, update_state 322 - 323 - update_state("onboarding", {"status": "complete", "path": "b"}) 324 - update_state( 325 - "journal", 326 - {"first_daily_ready": True, "first_daily_ready_at": "20260308T14:00:00"}, 327 - ) 328 - 329 - state = get_current() 330 - assert state["onboarding"]["status"] == "complete" 331 - assert state["onboarding"]["path"] == "b" 332 - assert state["journal"]["first_daily_ready"] is True 333 - 334 187 335 188 class TestComputeThickness: 336 189 """Tests for compute_thickness()."""
-73
tests/test_dream_preflight.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """Tests for dream preflight skip evaluation.""" 5 - 6 - import pytest 7 - 8 - 9 - @pytest.fixture 10 - def segment_dir(tmp_path, monkeypatch): 11 - journal = tmp_path / "journal" 12 - seg_dir = journal / "20240115" / "default" / "120000_300" 13 - seg_dir.mkdir(parents=True) 14 - (seg_dir / "agents").mkdir() 15 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(journal)) 16 - return seg_dir 17 - 18 - 19 - class TestShouldSkipPreflight: 20 - def test_daily_mode_never_skips(self): 21 - from think.dream import _should_skip_preflight 22 - 23 - assert _should_skip_preflight( 24 - "observation", 25 - day="20240115", 26 - segment=None, 27 - stream=None, 28 - ) == (False, None) 29 - 30 - def test_firstday_checkin_not_complete(self, monkeypatch): 31 - from think import awareness 32 - from think.dream import _should_skip_preflight 33 - 34 - monkeypatch.setattr( 35 - awareness, "get_onboarding", lambda: {"status": "observing"} 36 - ) 37 - assert _should_skip_preflight( 38 - "firstday_checkin", 39 - day="20240115", 40 - segment="120000_300", 41 - stream="default", 42 - ) == (True, "preflight:not_complete") 43 - 44 - def test_firstday_checkin_already_sent(self, monkeypatch): 45 - from think import awareness 46 - from think.dream import _should_skip_preflight 47 - 48 - monkeypatch.setattr( 49 - awareness, 50 - "get_onboarding", 51 - lambda: { 52 - "status": "complete", 53 - "firstday_checkin_sent": "20260402T10:00:00", 54 - }, 55 - ) 56 - assert _should_skip_preflight( 57 - "firstday_checkin", 58 - day="20240115", 59 - segment="120000_300", 60 - stream="default", 61 - ) == (True, "preflight:already_sent") 62 - 63 - def test_observation_not_observing(self, monkeypatch): 64 - from think import awareness 65 - from think.dream import _should_skip_preflight 66 - 67 - monkeypatch.setattr(awareness, "get_onboarding", lambda: {"status": "complete"}) 68 - assert _should_skip_preflight( 69 - "observation", 70 - day="20240115", 71 - segment="120000_300", 72 - stream="default", 73 - ) == (True, "preflight:not_observing")
-10
tests/test_dream_segment.py
··· 47 47 "type": "cogitate", 48 48 "schedule": "segment", 49 49 }, 50 - "observation": { 51 - "priority": 20, 52 - "type": "cogitate", 53 - "schedule": "segment", 54 - }, 55 - "firstday_checkin": { 56 - "priority": 20, 57 - "type": "cogitate", 58 - "schedule": "segment", 59 - }, 60 50 "pulse": { 61 51 "priority": 30, 62 52 "type": "cogitate",
-344
tests/test_observation.py
··· 1 - # SPDX-License-Identifier: AGPL-3.0-only 2 - # Copyright (c) 2026 sol pbc 3 - 4 - """Tests for the observation generator hooks and related features.""" 5 - 6 - import json 7 - 8 - import pytest 9 - 10 - 11 - @pytest.fixture(autouse=True) 12 - def _temp_journal(monkeypatch, tmp_path): 13 - """Isolate all tests to a temporary journal.""" 14 - monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 15 - 16 - 17 - class TestPreHook: 18 - def test_skips_when_not_observing(self): 19 - from talent.observation import pre_process 20 - 21 - result = pre_process({"day": "20260306", "segment": "120000_300"}) 22 - assert result == {"skip_reason": "not_observing"} 23 - 24 - def test_skips_when_status_is_ready(self): 25 - from talent.observation import pre_process 26 - from think.awareness import update_state 27 - 28 - update_state("onboarding", {"status": "ready"}) 29 - result = pre_process({"day": "20260306", "segment": "120000_300"}) 30 - assert result == {"skip_reason": "not_observing"} 31 - 32 - def test_skips_when_status_is_complete(self): 33 - from talent.observation import pre_process 34 - from think.awareness import update_state 35 - 36 - update_state("onboarding", {"status": "complete"}) 37 - result = pre_process({"day": "20260306", "segment": "120000_300"}) 38 - assert result == {"skip_reason": "not_observing"} 39 - 40 - def test_skips_when_status_is_skipped(self): 41 - from talent.observation import pre_process 42 - from think.awareness import update_state 43 - 44 - update_state("onboarding", {"status": "skipped"}) 45 - result = pre_process({"day": "20260306", "segment": "120000_300"}) 46 - assert result == {"skip_reason": "not_observing"} 47 - 48 - def test_proceeds_when_observing(self): 49 - from talent.observation import pre_process 50 - from think.awareness import start_onboarding 51 - 52 - start_onboarding("a") 53 - result = pre_process({"day": "20260306", "segment": "120000_300"}) 54 - assert result is None # No modifications — proceed with LLM 55 - 56 - 57 - class TestPostHook: 58 - @pytest.fixture(autouse=True) 59 - def _start_observation(self): 60 - from think.awareness import start_onboarding 61 - 62 - start_onboarding("a") 63 - 64 - def test_writes_observation_to_log(self): 65 - from talent.observation import post_process 66 - from think.awareness import read_log 67 - 68 - findings = json.dumps( 69 - { 70 - "has_meeting": False, 71 - "speaker_count": 1, 72 - "apps": ["VS Code"], 73 - "people": [], 74 - "companies": [], 75 - "projects": ["auth-service"], 76 - "tools": ["Git"], 77 - "topics": ["coding"], 78 - "summary": "Solo coding session", 79 - } 80 - ) 81 - 82 - post_process(findings, {"day": "20260306", "segment": "120000_300"}) 83 - 84 - entries = read_log("20260306") 85 - # Filter to observation entries (start_onboarding also writes a log entry) 86 - obs = [e for e in entries if e["kind"] == "observation"] 87 - assert len(obs) == 1 88 - assert obs[0]["data"]["apps"] == ["VS Code"] 89 - assert obs[0]["message"] == "Solo coding session" 90 - 91 - def test_increments_observation_count(self): 92 - from talent.observation import post_process 93 - from think.awareness import get_onboarding 94 - 95 - findings = json.dumps({"summary": "test", "has_meeting": False}) 96 - 97 - post_process(findings, {"day": "20260306", "segment": "120000_300"}) 98 - assert get_onboarding()["observation_count"] == 1 99 - 100 - post_process(findings, {"day": "20260306", "segment": "120500_300"}) 101 - assert get_onboarding()["observation_count"] == 2 102 - 103 - def test_handles_invalid_json(self): 104 - from talent.observation import post_process 105 - 106 - result = post_process("not json", {"day": "20260306", "segment": "120000_300"}) 107 - assert result == "not json" # Returns result unchanged 108 - 109 - def test_handles_non_dict_json(self): 110 - from talent.observation import post_process 111 - 112 - result = post_process("[1,2,3]", {"day": "20260306", "segment": "120000_300"}) 113 - assert result == "[1,2,3]" # Returns result unchanged 114 - 115 - def test_returns_result_unchanged(self): 116 - from talent.observation import post_process 117 - 118 - findings = json.dumps({"summary": "test", "has_meeting": False}) 119 - result = post_process(findings, {"day": "20260306", "segment": "120000_300"}) 120 - assert result == findings 121 - 122 - 123 - class TestNudgeLogic: 124 - def test_first_meeting_triggers_nudge(self): 125 - from talent.observation import _check_nudge 126 - 127 - findings = { 128 - "has_meeting": True, 129 - "speaker_count": 3, 130 - "meeting_topic": "sprint planning", 131 - } 132 - nudge = _check_nudge(findings, 1, 0, {}) 133 - assert nudge is not None 134 - assert "Meeting detected" in nudge["title"] 135 - assert "3 people" in nudge["message"] 136 - 137 - def test_no_meeting_no_first_nudge(self): 138 - from talent.observation import _check_nudge 139 - 140 - findings = {"has_meeting": False} 141 - nudge = _check_nudge(findings, 1, 0, {}) 142 - assert nudge is None 143 - 144 - def test_entity_cluster_triggers_nudge(self): 145 - from talent.observation import _check_nudge 146 - 147 - findings = { 148 - "has_meeting": False, 149 - "people": ["Alice", "Bob", "Charlie"], 150 - } 151 - nudge = _check_nudge(findings, 2, 1, {}) 152 - assert nudge is not None 153 - assert "network" in nudge["title"].lower() 154 - 155 - def test_progress_update_at_5_segments(self): 156 - from talent.observation import _check_nudge 157 - 158 - findings = {"has_meeting": False, "people": []} 159 - nudge = _check_nudge(findings, 5, 2, {}) 160 - assert nudge is not None 161 - assert "Still learning" in nudge["title"] 162 - 163 - def test_no_nudge_when_max_reached(self): 164 - from talent.observation import MAX_NUDGES, _check_nudge 165 - 166 - findings = {"has_meeting": True, "speaker_count": 5} 167 - # nudges_sent == MAX_NUDGES means all nudges used 168 - nudge = _check_nudge(findings, 1, MAX_NUDGES, {}) 169 - # MAX_NUDGES is checked in post_process, not _check_nudge 170 - # But _check_nudge with nudges_sent=4 won't match any trigger 171 - assert nudge is None 172 - 173 - 174 - class TestThreshold: 175 - def test_not_met_with_few_segments(self): 176 - from talent.observation import _threshold_met 177 - 178 - onboarding = {"started": "20260306T08:00:00"} 179 - assert _threshold_met(onboarding, 5) is False 180 - 181 - def test_not_met_with_short_time(self): 182 - # Just started — not enough time elapsed 183 - from datetime import datetime 184 - 185 - from talent.observation import MIN_SEGMENTS, _threshold_met 186 - 187 - now = datetime.now().strftime("%Y%m%dT%H:%M:%S") 188 - onboarding = {"started": now} 189 - assert _threshold_met(onboarding, MIN_SEGMENTS) is False 190 - 191 - def test_met_with_enough_segments_and_time(self): 192 - from talent.observation import MIN_SEGMENTS, _threshold_met 193 - 194 - # Started 5 hours ago 195 - onboarding = {"started": "20260101T03:00:00"} 196 - assert _threshold_met(onboarding, MIN_SEGMENTS) is True 197 - 198 - def test_not_met_with_no_started(self): 199 - from talent.observation import MIN_SEGMENTS, _threshold_met 200 - 201 - onboarding = {} 202 - assert _threshold_met(onboarding, MIN_SEGMENTS) is False 203 - 204 - 205 - class TestElapsedHours: 206 - def test_valid_iso(self): 207 - from talent.observation import _elapsed_hours 208 - 209 - # A date far in the past should give many hours 210 - hours = _elapsed_hours("20200101T00:00:00") 211 - assert hours > 24 212 - 213 - def test_empty_string(self): 214 - from talent.observation import _elapsed_hours 215 - 216 - assert _elapsed_hours("") == 0.0 217 - 218 - def test_invalid_format(self): 219 - from talent.observation import _elapsed_hours 220 - 221 - assert _elapsed_hours("not-a-date") == 0.0 222 - 223 - 224 - class TestAwarenessLogReadCLI: 225 - def test_log_read_empty(self): 226 - from typer.testing import CliRunner 227 - 228 - from apps.awareness.call import app 229 - 230 - result = CliRunner().invoke(app, ["log-read"]) 231 - assert result.exit_code == 0 232 - assert "No entries found" in result.output 233 - 234 - def test_log_read_with_entries(self): 235 - from typer.testing import CliRunner 236 - 237 - from apps.awareness.call import app 238 - from think.awareness import append_log 239 - 240 - append_log("observation", message="test finding") 241 - append_log("state", key="test.key") 242 - 243 - result = CliRunner().invoke(app, ["log-read"]) 244 - assert result.exit_code == 0 245 - data = json.loads(result.output) 246 - assert len(data) == 2 247 - 248 - def test_log_read_filter_by_kind(self): 249 - from typer.testing import CliRunner 250 - 251 - from apps.awareness.call import app 252 - from think.awareness import append_log 253 - 254 - append_log("observation", message="finding 1") 255 - append_log("state", key="transition") 256 - append_log("observation", message="finding 2") 257 - 258 - result = CliRunner().invoke(app, ["log-read", "--kind", "observation"]) 259 - assert result.exit_code == 0 260 - data = json.loads(result.output) 261 - assert len(data) == 2 262 - assert all(e["kind"] == "observation" for e in data) 263 - 264 - def test_log_read_with_limit(self): 265 - from typer.testing import CliRunner 266 - 267 - from apps.awareness.call import app 268 - from think.awareness import append_log 269 - 270 - for i in range(5): 271 - append_log("observation", message=f"finding {i}") 272 - 273 - result = CliRunner().invoke(app, ["log-read", "--limit", "2"]) 274 - assert result.exit_code == 0 275 - data = json.loads(result.output) 276 - assert len(data) == 2 277 - # Should return the LAST 2 entries 278 - assert data[0]["message"] == "finding 3" 279 - assert data[1]["message"] == "finding 4" 280 - 281 - 282 - class TestChatBarPlaceholder: 283 - def _get_placeholder(self): 284 - """Extract chat bar placeholder from the context processor.""" 285 - 286 - from flask import Flask 287 - 288 - app = Flask(__name__) 289 - app.config["TESTING"] = True 290 - 291 - from apps import AppRegistry 292 - from convey.apps import register_app_context 293 - 294 - registry = AppRegistry() 295 - register_app_context(app, registry) 296 - 297 - with app.test_request_context("/"): 298 - # Get context from context processors 299 - ctx = {} 300 - for func in app.template_context_processors[None]: 301 - ctx.update(func()) 302 - return ctx.get("chat_bar_placeholder", "") 303 - 304 - def test_default_placeholder(self): 305 - assert "Bring in past conversations" in self._get_placeholder() 306 - 307 - def test_observing_placeholder(self): 308 - from think.awareness import start_onboarding 309 - 310 - start_onboarding("a") 311 - placeholder = self._get_placeholder() 312 - assert "Bring in past conversations" in placeholder 313 - 314 - def test_ready_placeholder(self): 315 - from think.awareness import start_onboarding, update_state 316 - 317 - start_onboarding("a") 318 - update_state("onboarding", {"status": "ready"}) 319 - placeholder = self._get_placeholder() 320 - assert "Bring in past conversations" in placeholder 321 - 322 - def test_interviewing_placeholder(self): 323 - from think.awareness import start_onboarding 324 - 325 - start_onboarding("b") 326 - placeholder = self._get_placeholder() 327 - assert "Bring in past conversations" in placeholder 328 - 329 - def test_complete_placeholder(self): 330 - from think.awareness import start_onboarding, update_state 331 - 332 - start_onboarding("a") 333 - update_state("onboarding", {"status": "complete"}) 334 - update_state("imports", {"has_imported": True}) 335 - placeholder = self._get_placeholder() 336 - assert "Capture is running" in placeholder 337 - 338 - def test_skipped_placeholder(self): 339 - from think.awareness import skip_onboarding, update_state 340 - 341 - skip_onboarding() 342 - update_state("imports", {"has_imported": True}) 343 - placeholder = self._get_placeholder() 344 - assert "Capture is running" in placeholder
+3 -138
tests/test_onboarding.py tests/test_convey_apps.py
··· 1 1 # SPDX-License-Identifier: AGPL-3.0-only 2 2 # Copyright (c) 2026 sol pbc 3 3 4 - """Tests for onboarding routing logic.""" 4 + """Tests for convey app placeholder and attention behavior.""" 5 5 6 - import argparse 7 - from unittest.mock import MagicMock, patch 6 + from unittest.mock import patch 8 7 9 8 import pytest 10 9 from flask import Flask ··· 16 15 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 17 16 18 17 19 - class _ImmediateEvent: 20 - """Event object that never blocks in waits.""" 21 - 22 - def set(self) -> None: 23 - pass 24 - 25 - def wait(self, timeout: float | None = None) -> bool: 26 - return True 27 - 28 - 29 - def _run_chat_cli_main( 30 - args: argparse.Namespace, 31 - facets: dict, 32 - onboarding: dict | None = None, 33 - ) -> "MagicMock": 34 - with ( 35 - patch("think.chat_cli.setup_cli", return_value=args), 36 - patch("think.chat_cli.cortex_request", return_value="agent-1") as mock_request, 37 - patch( 38 - "think.chat_cli.read_agent_events", 39 - return_value=[{"event": "finish", "result": "ok"}], 40 - ), 41 - patch("think.chat_cli.threading.Event", return_value=_ImmediateEvent()), 42 - patch("think.chat_cli.CallosumConnection") as mock_connection, 43 - ): 44 - mock_conn = MagicMock() 45 - mock_connection.return_value = mock_conn 46 - 47 - import think.chat_cli as chat_cli 48 - 49 - chat_cli.main() 50 - 51 - return mock_request 52 - 53 - 54 - def _run_triage( 55 - onboarding: dict | None = None, 56 - ) -> "MagicMock": 18 + def _run_triage(): 57 19 """Run the triage endpoint with mocked state.""" 58 20 app = Flask(__name__) 59 21 with ( 60 - patch("think.awareness.get_onboarding", return_value=onboarding or {}), 61 22 patch("convey.utils.spawn_agent", return_value="agent-1") as mock_spawn, 62 23 patch("think.cortex_client.wait_for_agents", return_value=({}, [])), 63 24 patch( ··· 72 33 73 34 assert response.status_code == 200 74 35 return mock_spawn 75 - 76 - 77 - # --- Triage endpoint routing --- 78 - 79 - 80 - def test_triage_new_user_gets_onboarding(): 81 - """No facets, no awareness state → unified agent.""" 82 - mock = _run_triage() 83 - assert mock.call_args.kwargs["name"] == "unified" 84 - 85 - 86 - def test_triage_established_user_gets_unified(): 87 - """Onboarding complete → unified agent.""" 88 - mock = _run_triage(onboarding={"status": "complete"}) 89 - assert mock.call_args.kwargs["name"] == "unified" 90 - 91 - 92 - def test_triage_path_a_observing_gets_triage(): 93 - """Path A active → unified agent.""" 94 - mock = _run_triage(onboarding={"status": "observing"}) 95 - assert mock.call_args.kwargs["name"] == "unified" 96 - 97 - 98 - def test_triage_path_a_ready_gets_triage(): 99 - """Path A recommendations ready → unified agent.""" 100 - mock = _run_triage(onboarding={"status": "ready"}) 101 - assert mock.call_args.kwargs["name"] == "unified" 102 - 103 - 104 - def test_triage_skipped_gets_unified(): 105 - """Onboarding skipped, no facets → unified (single talent, no two-mode split).""" 106 - mock = _run_triage(onboarding={"status": "skipped"}) 107 - assert mock.call_args.kwargs["name"] == "unified" 108 - 109 - 110 - def test_triage_complete_gets_unified(): 111 - """Onboarding complete, no facets → unified (single talent, no two-mode split).""" 112 - mock = _run_triage(onboarding={"status": "complete"}) 113 - assert mock.call_args.kwargs["name"] == "unified" 114 - 115 - 116 - # --- Chat CLI routing --- 117 - 118 - 119 - def test_chat_cli_routes_to_onboarding_when_unified_and_no_facets(): 120 - """Unified talent stays unified when no facets exist.""" 121 - args = argparse.Namespace( 122 - message=["Hi there"], 123 - talent="unified", 124 - facet=None, 125 - provider=None, 126 - verbose=False, 127 - ) 128 - mock_request = _run_chat_cli_main(args, facets={}) 129 - assert mock_request.call_args.kwargs["name"] == "unified" 130 - 131 - 132 - def test_chat_cli_keeps_explicit_talent_when_no_facets(): 133 - args = argparse.Namespace( 134 - message=["Hi there"], 135 - talent="entities", 136 - facet=None, 137 - provider=None, 138 - verbose=False, 139 - ) 140 - mock_request = _run_chat_cli_main(args, facets={}) 141 - assert mock_request.call_args.kwargs["name"] == "entities" 142 - 143 - 144 - def test_chat_cli_path_a_observing_stays_unified(): 145 - """During Path A observation, chat CLI uses unified talent, not onboarding.""" 146 - args = argparse.Namespace( 147 - message=["What have you noticed?"], 148 - talent="unified", 149 - facet=None, 150 - provider=None, 151 - verbose=False, 152 - ) 153 - mock_request = _run_chat_cli_main( 154 - args, facets={}, onboarding={"status": "observing"} 155 - ) 156 - assert mock_request.call_args.kwargs["name"] == "unified" 157 - 158 - 159 - def test_chat_cli_skipped_stays_unified(): 160 - """After skipping onboarding, chat CLI uses unified talent.""" 161 - args = argparse.Namespace( 162 - message=["Hello"], 163 - talent="unified", 164 - facet=None, 165 - provider=None, 166 - verbose=False, 167 - ) 168 - mock_request = _run_chat_cli_main(args, facets={}, onboarding={"status": "skipped"}) 169 - assert mock_request.call_args.kwargs["name"] == "unified" 170 - 171 36 172 37 # --- Placeholder resolution --- 173 38
+2 -3
tests/test_talent_cli.py
··· 36 36 with_disabled = _collect_configs(include_disabled=True) 37 37 # include_disabled should return at least as many configs 38 38 assert len(with_disabled) >= len(without) 39 - for name in ("onboarding", "observation", "observation_review", "firstday_checkin"): 40 - assert name not in without 41 - assert name in with_disabled 39 + assert "flow" in without 40 + assert "flow" in with_disabled 42 41 43 42 44 43 def test_collect_configs_filter_schedule():
+4 -65
think/awareness.py
··· 3 3 4 4 """Awareness system — solstone's self-awareness about the user. 5 5 6 - Tracks the system's evolving understanding: onboarding state, observations, 7 - nudges, and interactions. Two-layer storage: 6 + Tracks the system's evolving understanding: capture state, identity 7 + persistence, imports, and awareness signals. Two-layer storage: 8 8 9 9 - ``awareness/current.json`` — materialized current state for fast reads 10 10 - ``awareness/YYYYMMDD.jsonl`` — append-only daily log of everything noticed 11 11 12 - Designed to extend beyond onboarding to cogitate (proactive agents), 12 + Designed to extend to cogitate (proactive agents), 13 13 learned preferences, and cross-session agent memory. 14 14 """ 15 15 ··· 519 519 return entries 520 520 521 521 522 - # --- Onboarding convenience functions --- 523 - 524 - 525 - def get_onboarding() -> dict[str, Any]: 526 - """Return the current onboarding state, or empty dict if none.""" 527 - return get_current().get("onboarding", {}) 528 - 529 - 530 - def start_onboarding(path: str) -> dict[str, Any]: 531 - """Record onboarding path selection. 532 - 533 - Parameters 534 - ---------- 535 - path : str 536 - "a" for passive observation, "b" for conversational interview 537 - 538 - Returns 539 - ------- 540 - dict 541 - The updated onboarding state 542 - """ 543 - status = "observing" if path == "a" else "interviewing" 544 - state = update_state( 545 - "onboarding", 546 - { 547 - "path": path, 548 - "status": status, 549 - "started": _now_iso(), 550 - "observation_count": 0, 551 - "nudges_sent": 0, 552 - }, 553 - ) 554 - append_log("state", key="onboarding.started", data={"path": path, "status": status}) 555 - return state 556 - 557 - 558 - def skip_onboarding() -> dict[str, Any]: 559 - """Record onboarding skip.""" 560 - state = update_state( 561 - "onboarding", 562 - { 563 - "status": "skipped", 564 - "started": _now_iso(), 565 - }, 566 - ) 567 - append_log("state", key="onboarding.skipped") 568 - return state 569 - 570 - 571 - def complete_onboarding() -> dict[str, Any]: 572 - """Record onboarding completion.""" 573 - state = update_state( 574 - "onboarding", 575 - { 576 - "status": "complete", 577 - }, 578 - ) 579 - append_log("state", key="onboarding.complete") 580 - return state 581 - 582 - 583 522 # --- Import tracking convenience functions --- 584 523 585 524 ··· 619 558 Returns a dict with five signals and a composite ``ready`` boolean: 620 559 621 560 - ``entity_depth``: count of entities with observation_depth >= 2 622 - - ``conversation_count``: non-onboarding conversation exchanges 561 + - ``conversation_count``: conversation exchanges excluding legacy onboarding 623 562 - ``recall_success``: exchanges where an entity name appears in agent_response 624 563 - ``facet_count``: number of enabled (non-muted) facets 625 564 - ``journal_days``: number of day directories with at least one segment
+13 -47
think/dream.py
··· 366 366 if not segment: 367 367 return (False, None) 368 368 369 - if prompt_name == "firstday_checkin": 370 - from think.awareness import get_onboarding 371 - 372 - onboarding = get_onboarding() 373 - if onboarding.get("status") != "complete": 374 - return (True, "preflight:not_complete") 375 - if onboarding.get("firstday_checkin_sent"): 376 - return (True, "preflight:already_sent") 377 - return (False, None) 378 - 379 - if prompt_name == "observation": 380 - from think.awareness import get_onboarding 381 - 382 - onboarding = get_onboarding() 383 - if onboarding.get("status") != "observing": 384 - return (True, "preflight:not_observing") 385 - return (False, None) 386 - 387 369 return (False, None) 388 370 389 371 ··· 595 577 speaker_config = _cfg("speaker_attribution") 596 578 if speaker_config: 597 579 agents_to_run.append(("speaker_attribution", speaker_config)) 598 - 599 - for onboarding_name in ("observation", "firstday_checkin"): 600 - onboarding_config = _cfg(onboarding_name) 601 - if onboarding_config: 602 - skip, _skip_reason = _should_skip_preflight( 603 - onboarding_name, 604 - day=day, 605 - segment=segment, 606 - stream=stream, 607 - ) 608 - if not skip: 609 - agents_to_run.append((onboarding_name, onboarding_config)) 610 580 611 581 total_expected = 1 + len(agents_to_run) 612 582 if recommend.get("pulse_update") and pulse_config: ··· 1914 1884 "speaker_attribution", 1915 1885 "if recommend.speaker_attribution + audio embeddings", 1916 1886 ), 1917 - ("observation", "if onboarding=observing"), 1918 - ("firstday_checkin", "if onboarding=complete"), 1919 1887 ("pulse", "if recommend.pulse_update"), 1920 1888 ]: 1921 1889 cfg = prompts.get(name) ··· 2544 2512 except Exception: 2545 2513 pass 2546 2514 2547 - # Set first_daily_ready awareness flag after first post-onboarding daily 2515 + # Set first_daily_ready awareness flag after first daily analysis 2548 2516 try: 2549 - from think.awareness import get_current, get_onboarding, update_state 2517 + from think.awareness import get_current, update_state 2550 2518 2551 - ob = get_onboarding() 2552 - if ob.get("status") == "complete": 2553 - cur = get_current() 2554 - if not cur.get("journal", {}).get("first_daily_ready"): 2555 - update_state( 2556 - "journal", 2557 - { 2558 - "first_daily_ready": True, 2559 - "first_daily_ready_at": datetime.now().strftime( 2560 - "%Y%m%dT%H:%M:%S" 2561 - ), 2562 - }, 2563 - ) 2519 + cur = get_current() 2520 + if not cur.get("journal", {}).get("first_daily_ready"): 2521 + update_state( 2522 + "journal", 2523 + { 2524 + "first_daily_ready": True, 2525 + "first_daily_ready_at": datetime.now().strftime( 2526 + "%Y%m%dT%H:%M:%S" 2527 + ), 2528 + }, 2529 + ) 2564 2530 except Exception: 2565 2531 pass 2566 2532