Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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