nyxcore-systems
8 min read

From Insight to Action: Building Smart Connections Across Projects with AI and tRPC

Dive into our recent development sprint where we built interconnected features – Action Points, Cross-Project Pattern Detection, and AI-powered Personas – to transform raw discussions into actionable project work across our platform.

FullstackAItRPCPrismaVector SearchProductivity ToolsNext.js

The journey from a casual team discussion to a concrete, trackable work item can often feel like crossing a chasm. Insights get lost, patterns go unnoticed, and the bridge between 'what we talked about' and 'what we need to do' remains unbuilt. This was the challenge we set out to tackle in our recent development session.

Our goal was ambitious: to implement a suite of interconnected features – Action Points, Cross-Project Pattern Detection, and AI-assisted Persona Management. Together, these would form a robust system for bridging the gap between raw discussions, extracted insights, and actionable work items, not just within a single project, but across an entire organization.

I'm excited to share a deep dive into how we brought these features to life, focusing on the technical decisions, the architecture, and a few lessons learned from a surprisingly smooth development process.

Laying the Foundation: The Data Schema

Every complex feature starts with a solid data model. For Action Points, we needed a comprehensive structure to capture their essence and context.

We introduced the ActionPoint model into our prisma/schema.prisma:

prisma
// prisma/schema.prisma
model ActionPoint {
  id               String   @id @default(cuid())
  tenantId         String
  userId           String
  projectId        String
  title            String
  description      String?
  category         String?
  priority         String?
  status           String   @default("OPEN")
  sourceDiscussionId String? // Link to original discussion
  sourceInsightId  String?   // Link to original insight
  isAutoDetected   Boolean  @default(false)
  detectedInProjects String[] // Projects where this pattern was detected
  workflowId       String?

  tenant Tenant @relation(fields: [tenantId], references: [id])
  user   User   @relation(fields: [userId], references: [id])
  project Project @relation(fields: [projectId], references: [id])
  workflow Workflow? @relation(fields: [workflowId], references: [id])

  @@index([tenantId, projectId, status])
  @@index([tenantId, isAutoDetected])
}

This model provides fields for tracking details, linking back to their origin (discussions or insights), and flagging whether they were automatically detected. Crucially, detectedInProjects allows us to track the reach of cross-project patterns.

We then established relations to User, Tenant, Project, and Workflow models, ensuring every action point is correctly attributed and contextualized. A new Row-Level Security (RLS) policy was also drafted for the action_points table to enforce tenant isolation, a critical security measure.

Understanding Our Users Better: Persona Management

Before diving deep into action, we wanted to ensure teams could truly understand who they were building for. This led to the Persona CRUD (Create, Read, Update, Delete) features, powered by AI.

We extended our src/server/trpc/routers/personas.ts with standard CRUD procedures, but the real magic happened with generateSuggestions. This procedure hooks into our new src/server/services/persona-generator.ts, an LLM-powered service designed to suggest personas based on user input.

typescript
// src/server/services/persona-generator.ts (simplified)
import { resolveWorkingProvider } from './llm-provider'; // Our dynamic LLM resolver

export async function generatePersonaSuggestions(description: string) {
  const llm = resolveWorkingProvider(); // Get the current best LLM
  const prompt = `Based on the following description, suggest three distinct user personas,
                  including their name, role, primary goal, and a key pain point.
                  Description: "${description}"`;
  const response = await llm.generateText(prompt);
  // Parse LLM response into structured persona suggestions
  return parsePersonaResponse(response);
}

The resolveWorkingProvider() pattern is key here, allowing us to dynamically switch between different LLM providers (e.g., OpenAI, Anthropic, local models) based on configuration or availability, ensuring resilience and flexibility.

On the frontend, we built a comprehensive persona experience:

  • A List page (src/app/(dashboard)/dashboard/personas/page.tsx) with grid cards, showing built-in and custom personas.
  • A 3-phase AI-assisted creation flow (src/app/(dashboard)/dashboard/personas/new/page.tsx): discovery (free-text input for AI suggestions) → selection (choose from AI-generated or refine) → customize (final tweaks).
  • A Detail/edit page (src/app/(dashboard)/dashboard/personas/[id]/page.tsx), read-only for built-in personas.

This makes persona creation less of a chore and more of an insightful, guided process.

Making Insights Actionable: Action Points CRUD

With personas in place, the next step was to make 'action' a first-class citizen. Our src/server/trpc/routers/action-points.ts now exposes 8 procedures, covering everything from listing all action points (listAll) to creating new ones (create) and even generating workflows from them (createWorkflow).

A standout feature here is extractFromDiscussion. This procedure leverages src/server/services/action-point-extraction.ts, another LLM-powered service that can parse discussion messages and intelligently identify potential action points.

typescript
// src/server/services/action-point-extraction.ts (simplified)
import { resolveWorkingProvider } from './llm-provider';

export async function extractActionPointsFromDiscussion(discussionText: string, projectId: string, userId: string) {
  const llm = resolveWorkingProvider();
  const prompt = `Review the following discussion transcript and identify any clear action points.
                  For each action point, provide a concise title, a brief description,
                  and suggest a category (e.g., 'Bug', 'Feature', 'Refactor', 'Research').
                  Format your response as a JSON array.
                  Discussion: "${discussionText}"`;
  const llmResponse = await llm.generateStructuredOutput(prompt);
  // Validate and persist extracted action points
  return llmResponse.map(ap => ({ ...ap, projectId, userId, isAutoDetected: true }));
}

This significantly reduces manual effort, transforming unstructured conversation into structured, actionable items. We integrated these capabilities into the project dashboard, adding an "Actions" tab with components for creating and extracting action points.

The Intelligent Bridge: Cross-Project Pattern Detection

This is where things get really interesting. One of the biggest challenges in larger organizations is identifying recurring themes or problems across different projects. Our new src/server/services/cross-project-scanner.ts addresses this head-on.

This service performs vector similarity search on new, high-severity insights or knowledge exports. If it finds similar patterns (with a configurable threshold of 0.65) in other projects, it automatically creates new ActionPoint records in those projects, flagged with isAutoDetected: true. It also de-duplicates by sourceInsightId and projectId to prevent noise.

typescript
// src/server/services/cross-project-scanner.ts (simplified)
import { getVectorEmbedding } from './embedding-service'; // Our embedding provider
import { prisma } from '../db'; // Our Prisma client

export async function triggerCrossProjectScan(insightId: string, insightText: string, sourceProjectId: string) {
  const embedding = await getVectorEmbedding(insightText);

  // Find similar insights across all projects
  const similarInsights = await prisma.$queryRaw`
    SELECT id, projectId, embedding
    FROM insights
    WHERE project_id != ${sourceProjectId} AND id != ${insightId}
    ORDER BY embedding <=> ${embedding}
    LIMIT 10; // Get top 10 similar insights
  `;

  for (const similar of similarInsights) {
    // Calculate cosine similarity, filter by threshold
    if (calculateCosineSimilarity(embedding, similar.embedding) > 0.65) {
      // Create auto-detected action point for the other project
      await prisma.actionPoint.create({
        data: {
          title: `Detected pattern from ${sourceProjectId}`,
          description: `Similar to insight ${insightId} from project ${sourceProjectId}.`,
          projectId: similar.projectId,
          // ... other fields
          isAutoDetected: true,
          detectedInProjects: [sourceProjectId],
          sourceInsightId: insightId,
        },
      });
    }
  }
}

This scanner is triggered as a fire-and-forget hook after a high-severity insight is persisted (src/server/services/insight-persistence.ts) and after knowledge is exported from a discussion (src/server/services/discussion-knowledge.ts). This ensures that the system is constantly learning and connecting dots in the background without blocking user workflows.

The Unified View: Top-Level Action Points Page

Finally, all these interconnected pieces converge on a new top-level "Action Points" page (src/app/(dashboard)/dashboard/action-points/page.tsx). This page provides a comprehensive, grouped-by-project view of all action points, with powerful filters for status and auto-detection.

Users can easily cycle through statuses, create workflows directly from action points, and see priority/category badges at a glance. It's the central hub for understanding what needs to be done, where, and how it relates to broader organizational patterns.

Smooth Sailing: A Testament to Planning

One of the most satisfying aspects of this session was the "Pain Log" (or lack thereof!). The entire development process, involving 8 new files and 8 modified files across multiple architectural layers, completed cleanly:

  • Zero major issues encountered.
  • Both parallel agents completed cleanly with zero overlapping file conflicts. This speaks volumes about clear task decomposition and good communication.
  • npx tsc --noEmit passed on the first attempt after all changes were integrated. This is a powerful validation of our commitment to TypeScript and type safety, catching potential bugs before they even hit runtime.

This experience reinforces the value of meticulous planning, a robust type system, and well-defined interfaces when tackling complex, interconnected features.

Immediate Next Steps

While the core implementation is solid, the immediate next steps involve rigorous testing and final touches:

  1. Apply RLS Policy: Manually activate the new action_points RLS policy: psql -f prisma/rls.sql.
  2. Manual QA: Thoroughly test the entire flow: persona creation (AI-assisted), manual action point creation, LLM-based action point extraction from discussions, workflow creation, and the top-level action points page.
  3. Cross-Project Scanner Test: Verify that persisting a high-severity insight correctly triggers auto-detected action points in other projects.
  4. Unit Tests: Consider adding dedicated unit tests for cross-project-scanner.ts and action-point-extraction.ts to ensure the AI-driven logic remains robust.

Conclusion

This sprint was a significant leap forward in making our platform more intelligent and actionable. By weaving together Action Points, AI-powered Personas, and Cross-Project Pattern Detection, we've built a system that not only captures insights but actively transforms them into a structured, trackable, and discoverable work. It's exciting to see how these intelligent connections will empower teams to move faster and make more informed decisions.

json
{
  "thingsDone": [
    "Implemented ActionPoint data model with Prisma",
    "Developed AI-assisted Persona CRUD with dynamic LLM provider",
    "Created Action Points CRUD, including LLM-powered extraction from discussions",
    "Built Cross-Project Scanner using vector similarity search for auto-detection",
    "Created a unified Top-Level Action Points dashboard with filtering and workflow integration",
    "Integrated RLS for tenant isolation on new tables"
  ],
  "pains": [
    "No major issues encountered, development was remarkably smooth."
  ],
  "successes": [
    "All 5 development phases completed on schedule",
    "Zero overlapping file conflicts during parallel development",
    "TypeScript type-checking passed on first attempt (`npx tsc --noEmit`)",
    "Seamless integration of LLM services via `resolveWorkingProvider()` pattern",
    "Successful implementation of complex cross-project pattern detection"
  ],
  "techStack": [
    "Next.js",
    "tRPC",
    "Prisma",
    "PostgreSQL",
    "TypeScript",
    "LLMs (via dynamic provider)",
    "Vector Similarity Search"
  ]
}