Skip to content

Action Workspace & AI Briefings

The action workspace replaces the default dashboard landing with an AI-driven view that surfaces what matters most. It generates prioritized briefings, suggests concrete actions, and provides deep-dive analysis — all updated daily at 5am local time.

When you open the dashboard (/), the WorkspaceView loads five resources in parallel:

  • Workspace summary — badge counts for unanswered messages, overdue tasks, today’s tasks, and missed calls
  • Needs attention — detailed lists behind each badge
  • AI briefing — cached summary with ranked actions
  • Detailed analysis — agentic deep-dive with entity-linked sections
  • Engagement stats — recent activity feed

Each section renders independently with skeleton states — no waterfall.

pub struct DashboardBriefing {
pub briefing_text: String, // Markdown with bold highlights
pub actions: Vec<BriefingAction>,
pub generated_at: String, // Formatted in org timezone
}
pub struct BriefingAction {
pub priority: String, // "urgent" | "high" | "normal"
pub action_type: String, // "follow_up" | "reply" | "return_call" | "complete_task" | "reconnect"
pub title: String,
pub reason: String,
pub contact_id: Option<Uuid>,
pub task_id: Option<Uuid>,
pub call_id: Option<Uuid>,
pub suggested_actions: Vec<String>, // ["call", "message", "done", "open", "reply"]
}
pub struct DashboardDetailedBriefing {
pub detailed_text: String, // Markdown with ## sections per contact
pub mentioned_contacts: Vec<MentionedContact>,
pub generated_at: String,
}
pub struct MentionedContact {
pub contact_id: Uuid,
pub name: String,
pub section_title: String,
pub urgency: String, // "urgent" | "high" | "normal"
}
pub struct WorkspaceSummary {
pub unanswered_count: u32,
pub overdue_tasks_count: u32,
pub today_tasks_count: u32,
pub missed_calls_count: u32,
}
pub struct WorkspaceNeedsAttention {
pub overdue_tasks: Vec<WorkspaceTask>,
pub today_tasks: Vec<WorkspaceTask>,
pub unanswered_contacts: Vec<WorkspaceUnansweredContact>,
pub missed_calls: Vec<WorkspaceMissedCall>,
}

Each list returns up to 20 items with entity references (contact IDs, task IDs, call IDs) for linking.

MethodPathDescription
GET/api/dashboard/workspace-summaryBadge counts
GET/api/dashboard/workspace-needs-attentionDetailed lists behind badges
GET/api/dashboard/briefingCached AI briefing (returns null if not yet generated)
POST/api/dashboard/briefing/generateTrigger fresh AI briefing generation
GET/api/dashboard/detailed-briefingCached detailed analysis
POST/api/dashboard/detailed-briefing/generateTrigger agentic deep-dive (requires briefing to exist)

All endpoints enforce permissions via resolve_dashboard_scope(). Users without messaging permissions see unanswered_count: 0 instead of an error.

generate_dashboard_briefing collects context, sends it to an LLM, and upserts the result:

  1. Collect contextcollect_briefing_context() gathers the top 10 unanswered contacts, overdue tasks, today’s tasks, missed calls (last 7 days), and contacts going cold (7+ days since last contact)
  2. Build prompt — system prompt instructs the LLM to produce a markdown summary with 5–8 ranked actions
  3. Generate — calls generate_text() with structured output schema BriefingOutput
  4. Upsert — stores in dashboard_briefing table using a unique index on member_id
  5. Track usage — logs via spawn_log_ai_usage(AiUsageFeature::DashboardBriefing)

Actions are ranked by priority:

  • Urgent — overdue items, 24h+ unanswered messages
  • High — due today, 7+ days since last contact
  • Normal — routine follow-ups

generate_detailed_briefing performs an agentic investigation using read-only tools:

  1. Load prerequisites — requires a summary briefing to exist (returns error otherwise)
  2. Build tool setget_bulk_contact_memory, get_bulk_contact_messages, get_call_details, get_contact_details, get_tasks
  3. Investigate — LLM uses tools (max 10 steps) to deep-dive the top 3–5 contacts
  4. Entity linking — every contact, task, and call in the output is a markdown link: [Name](/contacts/{id})
  5. Upsert — stores in dashboard_detailed_briefing table

GenerateScheduledBriefingsJob runs every hour (cron: 0 0 * * * * *) and generates briefings at 5am local time per organization.

Flow:

  1. Find organizations with members active in the last 3 days
  2. Batch-fetch org timezones from organization_profile
  3. For each org where the local hour is 5am:
    • Fetch active members and their permissions
    • Skip members who already have a briefing generated today
    • Build a synthetic Session via Session::from_models()
    • Call generate_dashboard_briefing() per member

Failures are isolated — one member’s error doesn’t block others.

// Key constants
const BRIEFING_TARGET_HOUR: u32 = 5; // 5am local time
const ACTIVE_DAYS: i64 = 3; // Only active members

Two tables with upsert semantics via unique indexes on member_id:

dashboard_briefingbriefing_text (text), actions_json (jsonb), generated_at (timestamptz), FK to member and organization

dashboard_detailed_briefingdetailed_text (text), mentioned_contacts_json (jsonb, default []), generated_at (timestamptz), FK to member and organization

Both use ON CONFLICT (member_id) update semantics — each member has at most one row that gets overwritten on regeneration.

Renders three states:

  • Generating — skeleton animation
  • Empty — CTA button “Get your AI briefing”
  • Loaded — gradient card with markdown briefing, relative timestamp, and refresh button

Actions display in a grouped list by priority (Urgent → High → Follow Up). Each action row shows the title, reason, and inline buttons based on suggested_actions (call, message, done, open, reply).

Collapsible card with:

  • Quick-links bar — urgency-colored badges for mentioned contacts
  • Markdown content — entity-linked sections per contact with ## [Name](/contacts/{id}) — Situation headings
  • SPA navigationintercept_markdown_links() hooks click events on entity links to prevent full page reloads

Requires a summary briefing before generation. Shows a disabled CTA if no summary exists.

Collapsible section with four tabs: overdue tasks, today’s tasks, unanswered contacts, and missed calls. Each tab shows a list with entity links and action buttons.

src/mods/dashboard/
├── api/
│ ├── get_workspace_summary_api.rs
│ ├── get_workspace_needs_attention_api.rs
│ ├── get_dashboard_briefing_api.rs
│ ├── generate_dashboard_briefing_api.rs
│ ├── get_dashboard_detailed_briefing_api.rs
│ └── generate_dashboard_detailed_briefing_api.rs
├── components/
│ ├── ai_briefing_card_component.rs
│ ├── detailed_analysis_card_component.rs
│ ├── workspace_greeting_component.rs
│ ├── workspace_needs_attention_component.rs
│ ├── quick_actions_component.rs
│ └── recent_activity_feed_component.rs
├── jobs/
│ └── generate_scheduled_briefings_job.rs
├── services/
│ ├── collect_briefing_context_service.rs
│ ├── generate_dashboard_briefing_service.rs
│ ├── generate_detailed_briefing_service.rs
│ ├── generate_scheduled_briefings_service.rs
│ ├── get_workspace_summary_service.rs
│ └── get_workspace_needs_attention_service.rs
├── types/
│ ├── dashboard_briefing_type.rs
│ ├── dashboard_detailed_briefing_type.rs
│ ├── workspace_summary_type.rs
│ └── workspace_needs_attention_type.rs
└── views/
└── workspace_view.rs