personal memory agent
0
fork

Configure Feed

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

feat(health): surface pipeline anomalies via CLI, home vitals, briefing

Wire three consumers onto think.pipeline_health:

- sol call health pipeline [--day | --yesterday] emits the full
summary as pretty JSON for ad-hoc inspection and briefing audits.
- _build_pulse_context adds pipeline_status, rendered as a subdued
warning line in pulse-vitals between attention and the health link;
healthy days stay silent. refreshVitals mirrors the same slot.
- morning_briefing.md calls `sol call health pipeline --yesterday`
in Phase 1, audits its output in Phase 1.5, and prepends
pipeline gaps above Overdue commitments in Phase 2 when present.

+144
+37
apps/health/call.py
··· 1 + # SPDX-License-Identifier: AGPL-3.0-only 2 + # Copyright (c) 2026 sol pbc 3 + 4 + """Health diagnostics — exposes `sol call health <command>` entry points.""" 5 + 6 + from __future__ import annotations 7 + 8 + import json 9 + from datetime import datetime, timedelta 10 + from typing import Optional 11 + 12 + import typer 13 + 14 + from think.pipeline_health import summarize_pipeline_day 15 + 16 + app = typer.Typer(help="Health diagnostics — sol call health ...") 17 + 18 + 19 + @app.command("pipeline") 20 + def pipeline( 21 + day: Optional[str] = typer.Option(None, "--day", help="Day to summarize (YYYYMMDD)."), 22 + yesterday: bool = typer.Option(False, "--yesterday", help="Summarize yesterday's pipeline."), 23 + ) -> None: 24 + """Summarize dream pipeline health for one day.""" 25 + if day is not None and yesterday: 26 + typer.echo("--day and --yesterday are mutually exclusive", err=True) 27 + raise typer.Exit(1) 28 + 29 + if day is not None: 30 + target = day 31 + elif yesterday: 32 + target = (datetime.now() - timedelta(days=1)).strftime("%Y%m%d") 33 + else: 34 + target = datetime.now().strftime("%Y%m%d") 35 + 36 + summary = summarize_pipeline_day(target) 37 + typer.echo(json.dumps(summary, indent=2, sort_keys=False))
+80
apps/health/tests/test_call.py
··· 1 + # SPDX-License-Identifier: AGPL-3.0-only 2 + # Copyright (c) 2026 sol pbc 3 + 4 + """Tests for health app call commands.""" 5 + 6 + from __future__ import annotations 7 + 8 + import json 9 + from datetime import datetime, timedelta 10 + 11 + from typer.testing import CliRunner 12 + 13 + from apps.health.call import app 14 + 15 + runner = CliRunner() 16 + 17 + 18 + def test_pipeline_default_is_today(health_env): 19 + health_env() 20 + 21 + result = runner.invoke(app, []) 22 + 23 + assert result.exit_code == 0 24 + payload = json.loads(result.stdout) 25 + assert payload["day"] == datetime.now().strftime("%Y%m%d") 26 + 27 + 28 + def test_pipeline_day_option(health_env): 29 + health_env() 30 + 31 + result = runner.invoke(app, ["--day", "20250101"]) 32 + 33 + assert result.exit_code == 0 34 + payload = json.loads(result.stdout) 35 + assert payload["day"] == "20250101" 36 + 37 + 38 + def test_pipeline_yesterday_option(health_env): 39 + health_env() 40 + 41 + result = runner.invoke(app, ["--yesterday"]) 42 + 43 + assert result.exit_code == 0 44 + payload = json.loads(result.stdout) 45 + assert payload["day"] == (datetime.now() - timedelta(days=1)).strftime("%Y%m%d") 46 + 47 + 48 + def test_mutual_exclusion_error(health_env): 49 + health_env() 50 + 51 + result = runner.invoke(app, ["--day", "20250101", "--yesterday"]) 52 + 53 + assert result.exit_code == 1 54 + assert "mutually exclusive" in result.stderr 55 + 56 + 57 + def test_pipeline_with_real_fixture(health_env): 58 + env = health_env() 59 + day = "20260101" 60 + health_path = env.journal / day / "health" / "123_segment_dream.jsonl" 61 + health_path.parent.mkdir(parents=True, exist_ok=True) 62 + health_path.write_text( 63 + "\n".join( 64 + [ 65 + json.dumps({"event": "run.start", "mode": "segment"}), 66 + json.dumps({"event": "agent.dispatch", "mode": "segment"}), 67 + json.dumps({"event": "agent.complete", "mode": "segment"}), 68 + json.dumps({"event": "run.complete", "mode": "segment", "duration_ms": 42}), 69 + ] 70 + ) 71 + + "\n", 72 + encoding="utf-8", 73 + ) 74 + 75 + result = runner.invoke(app, ["--day", day]) 76 + 77 + assert result.exit_code == 0 78 + payload = json.loads(result.stdout) 79 + assert payload["runs"]["segment"]["count"] == 1 80 + assert payload["agents"]["dispatched"] >= 1
+9
apps/home/routes.py
··· 22 22 from think.awareness import get_current 23 23 from think.facets import get_enabled_facets, get_facets 24 24 from think.indexer.journal import get_journal_index 25 + from think.pipeline_health import pipeline_status_message, summarize_pipeline_day 25 26 from think.utils import get_journal 26 27 27 28 # Briefing phase thresholds ··· 765 766 briefing_sections, len(briefing_needs_deduped) 766 767 ) 767 768 769 + try: 770 + _summary = summarize_pipeline_day(_today()) 771 + pipeline_status = pipeline_status_message(_summary) 772 + except Exception: 773 + logger.warning("pipeline_status unavailable", exc_info=True) 774 + pipeline_status = None 775 + 768 776 return { 769 777 "today": today, 770 778 "now": now, 771 779 "capture_status": capture_status, 772 780 "last_observe_relative": last_observe_relative, 773 781 "attention": attention, 782 + "pipeline_status": pipeline_status, 774 783 "segment_count": segment_count, 775 784 "duration_minutes": duration_minutes, 776 785 "facet_data": facet_data,
+7
apps/home/workspace.html
··· 945 945 {{ attention.placeholder_text }} 946 946 </div> 947 947 {% endif %} 948 + {% if pipeline_status %} 949 + <div class="pulse-vitals-sep"></div> 950 + <div class="pulse-vitals-item pulse-vitals-attention">{{ pipeline_status.message }}</div> 951 + {% endif %} 948 952 <a href="/app/health">health →</a> 949 953 </div> 950 954 ··· 1444 1448 } 1445 1449 if (data.attention) { 1446 1450 html += '<div class="pulse-vitals-sep"></div><div class="pulse-vitals-item pulse-vitals-attention">' + esc(data.attention.placeholder_text) + '</div>'; 1451 + } 1452 + if (data.pipeline_status) { 1453 + html += '<div class="pulse-vitals-sep"></div><div class="pulse-vitals-item pulse-vitals-attention">' + esc(data.pipeline_status.message) + '</div>'; 1447 1454 } 1448 1455 html += '<a href="/app/health">health →</a>'; 1449 1456 el.innerHTML = html;
+11
talent/morning_briefing.md
··· 34 34 35 35 For each person appearing in today's calendar events, also run: 36 36 11. `sol call entities intelligence PERSON --brief` — relationship context, recent interactions, observations (brief mode: last 20 signals + top 20 network, ~95% smaller payload) 37 + 12. `sol call health pipeline --yesterday` — pipeline anomalies from yesterday's processing 37 38 38 39 ## Phase 1.5: Pre-pass audit 39 40 ··· 46 47 - `facet_newsletters` — facets that returned a newsletter (step 2) 47 48 - `followups` — follow-up items returned (step 6) 48 49 - `todos` — pending todo items (step 4) 50 + - `pipeline_anomalies` — surfaced anomalies from yesterday's pipeline summary 49 51 50 52 2. **Identify gaps.** Record a gap for each source that returned zero results or is otherwise missing. A gap is not an error — it means the briefing has a blind spot in that area. Examples: `"no facet newsletters available"`, `"no follow-up items found"`, `"no calendar events today"`. 51 53 52 54 3. **Catalog tool errors.** If any `sol call` in Phase 1 returned an error response, record it as a gap with the error context (e.g., `"entity intelligence failed for Sarah Chen"`). 55 + 56 + 4. **Check pipeline health.** If yesterday's pipeline summary status is not healthy, surface its anomalies as top-ranked operational gaps in Needs Attention. If status is healthy, omit the Pipeline gaps section entirely. 53 57 54 58 > **CRITICAL: Tool error handling.** When any `sol call` tool returns an error, you MUST: 55 59 > 1. Record the error as a gap (e.g., `"entity intelligence failed for Sarah Chen"`) ··· 81 85 Grade highlights by evidence strength. **High** (corroborated by multiple sources — e.g., newsletter + decision + transcript): state assertively — "Shipped the entity pipeline refactor." **Medium** (single source, clear statement): attribute and present directly — "Closed three PRs on the data pipeline ([work newsletter](sol://...))." **Low** (inferred from ambiguous context, single passing mention): hedge — "Possible progress on the auth migration" or "May have discussed budget reallocation." When upstream decision output includes a `Confidence:` score, use it to inform grading: 0.85+ high, 0.50–0.84 medium, below 0.50 low. Never hedge items corroborated by multiple sources; never state single-mention inferences assertively. 82 86 83 87 **Needs Attention** — Ranked action list. Synthesize from all sources into a single prioritized list: 88 + 0. Pipeline gaps from yesterday's processing 84 89 1. Overdue commitments (todos past due, missed follow-ups) 85 90 2. Pending follow-ups (items flagged by the followups agent) 86 91 3. Relationship maintenance (entities not contacted recently who are relevant) 87 92 4. Unscheduled todos (action items with no calendar time blocked) 93 + Pipeline gaps owner-facing phrasings (from `pipeline_anomalies`): 94 + - `activity_agents_missing` → "Activity agents didn't fire yesterday — detected activities weren't processed." 95 + - `daily_agents_missing` → "Daily dream agents didn't run yesterday." 96 + - `agent_failure` → "N dream agent(s) failed yesterday: <names>." 97 + 98 + Do NOT include this section when pipeline status is `healthy` (status == "healthy" or anomalies list is empty). 88 99 Attribute commitments and follow-ups to the originating segment: `(committed [date](sol://...))`, `(flagged [date](sol://...))`. For relationship items: `(last interaction [date])`. For inferred items: `(inferred from [source](sol://...))`. 89 100 Grade action items by evidence strength. **High** (explicit commitment with date, or overdue todo): state assertively — "Follow up on Series A term sheet — committed March 20, now overdue." **Medium** (flagged by followups agent with moderate confidence, or clear single-source item): present with attribution — "Review CI pipeline logs (flagged yesterday)." **Low** (inferred obligation from ambiguous mention, or low-confidence followup): hedge — "Possible commitment to send deck to investors" or "May need to follow up on the API discussion." When upstream followup output includes a `Confidence:` score, use it: 0.85+ high, 0.50–0.84 medium, below 0.50 low. Never hedge explicit commitments with clear dates; never present inferred obligations as definite action items. 90 101