Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ As usage grows, the platform needs stronger derived data pipelines, performance

- [x] MCP sidecar with on-demand stats, friction reports, and recommendations
- [x] [P0] Proactive coaching skill that activates at session start with contextual suggestions
- [ ] [P0] Live session signals that stream friction, satisfaction, and risk as work happens
- [x] [P0] Live session signals that stream friction, satisfaction, and risk as work happens
- [ ] [P1] In-session workflow nudges based on project playbooks and prior failures
- [ ] [P1] Daily and weekly personal recaps inside the sidecar
- [ ] [P2] Lightweight session planning prompts before complex work begins
Expand Down
22 changes: 22 additions & 0 deletions src/primer/common/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -2203,6 +2203,28 @@ class MaturityAnalyticsResponse(BaseModel):
model_diversity_avg: float = 0.0


# --- Live Session Signals ---


class LiveSessionSignal(BaseModel):
signal_type: str
severity: Literal["info", "warning", "critical"]
title: str
detail: str
evidence: dict[str, Any] = Field(default_factory=dict)


class LiveSessionSignalsResponse(BaseModel):
session_id: str
agent_type: str
project_name: str | None = None
started_at: datetime | None = None
total_messages: int = 0
risk_level: Literal["low", "medium", "high"]
satisfaction_signal: Literal["positive", "neutral", "negative", "unknown"] = "unknown"
signals: list[LiveSessionSignal] = Field(default_factory=list)


# --- Coaching Brief ---


Expand Down
14 changes: 14 additions & 0 deletions src/primer/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from primer.mcp.tools import (
primer_coaching,
primer_friction_report,
primer_live_session_signals,
primer_my_stats,
primer_recommendations,
primer_session_start_coaching,
Expand Down Expand Up @@ -93,5 +94,18 @@ def session_start_coaching(
)


@mcp.tool()
def live_session_signals(
session_id: str | None = None,
transcript_path: str | None = None,
) -> str:
"""Get live friction, satisfaction, and risk signals for the current local session.

Primer inspects the in-progress local transcript and summarizes whether the
session looks healthy, stuck, or at risk of abandonment.
"""
return primer_live_session_signals(session_id=session_id, transcript_path=transcript_path)


if __name__ == "__main__":
mcp.run()
27 changes: 27 additions & 0 deletions src/primer/mcp/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import httpx

from primer.mcp.sync import sync_sessions
from primer.server.services.live_session_signal_service import get_live_session_signals

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -178,3 +179,29 @@ def primer_session_start_coaching(
return f"Error: {resp.status_code} - {resp.text}"
except httpx.RequestError as e:
return f"Error connecting to server: {e}"


def primer_live_session_signals(
session_id: str | None = None,
transcript_path: str | None = None,
) -> str:
"""Analyze the current in-progress local session for live risk and friction signals.

This is computed locally from the active transcript so it can be called repeatedly
during a session without waiting for end-of-session ingest.
"""
try:
data = get_live_session_signals(session_id=session_id, transcript_path=transcript_path)
except ValueError as exc:
return f"Error: {exc}"
lines = ["## Live Session Signals\n"]
lines.append(f"**Risk**: {data.risk_level}\n")
lines.append(f"**Satisfaction**: {data.satisfaction_signal}\n")
if data.project_name:
lines.append(f"**Project**: {data.project_name}\n")
lines.append(f"**Session**: {data.session_id} ({data.agent_type})\n")
for signal in data.signals:
lines.append(f"### [{signal.severity}] {signal.title}")
lines.append(f"- {signal.detail}")
lines.append("")
return "\n".join(lines)
Loading
Loading