Skip to main content

Overview

The intel module manages research data fragments collected by agents. Each fragment represents a piece of intel from a specific source (e.g., LinkedIn, Twitter, Exa). Import path:
import { api } from '@/convex/_generated/api';

Queries

getByPerson

Retrieve all intel fragments for a specific person. Type signature:
getByPerson: (args: {
  personId: Id<"persons">;
}) => Promise<IntelFragment[]>
Parameters:
personId
Id<'persons'>
required
Person document ID.
Usage:
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';

function IntelList({ personId }: { personId: Id<"persons"> }) {
  const intel = useQuery(api.intel.getByPerson, { personId });
  
  return (
    <div>
      <h3>Research Data ({intel?.length} fragments)</h3>
      {intel?.map(fragment => (
        <div key={fragment._id}>
          <strong>[{fragment.source}]</strong> {fragment.content}
        </div>
      ))}
    </div>
  );
}
Returns: Array of intel fragments, indexed by person for fast lookup.

recentActivity

Retrieve the 50 most recent activity log entries across all persons. Type signature:
recentActivity: () => Promise<ActivityLog[]>
Usage:
function ActivityFeed() {
  const activity = useQuery(api.intel.recentActivity);
  
  return (
    <div>
      <h2>Recent Activity</h2>
      {activity?.map(entry => (
        <div key={entry._id}>
          <span className={`badge ${entry.type}`}>{entry.type}</span>
          {entry.message}
          <time>{new Date(entry.timestamp).toLocaleString()}</time>
        </div>
      ))}
    </div>
  );
}
Returns: Activity log entries in descending chronological order (most recent first).

Mutations

create

Create a new intel fragment. Type signature:
create: (args: {
  personId: Id<"persons">;
  source: string;
  dataType: string;
  content: string;
  verified?: boolean;
}) => Promise<Id<"intelFragments">>
Parameters:
personId
Id<'persons'>
required
Person this intel relates to.
source
string
required
Source identifier (e.g., "linkedin", "twitter", "exa", "google").
dataType
string
required
Type of intel:
  • "profile" - Profile/bio data
  • "work_history" - Employment information
  • "education" - Educational background
  • "social" - Social media activity
  • "news" - News mentions
  • "agent_event" - Agent status updates
  • "other" - Uncategorized data
content
string
required
Raw intel content (text snippet, JSON string, or URL).
verified
boolean
default:false
Whether this intel has been verified/cross-referenced.
Returns: Convex document ID of the created intel fragment. Usage:
import { useMutation } from 'convex/react';
import { api } from '@/convex/_generated/api';

function AddIntel({ personId }: { personId: Id<"persons"> }) {
  const createIntel = useMutation(api.intel.create);
  
  const handleAdd = async () => {
    await createIntel({
      personId,
      source: 'linkedin',
      dataType: 'work_history',
      content: 'Senior Engineer at TechCorp (2020-Present)',
      verified: true
    });
  };
  
  return <button onClick={handleAdd}>Add Intel</button>;
}
Side effects:
  • Records timestamp
  • Creates activity log entry with type "research"
  • Indexes by personId for fast queries

Schema

IntelFragment

interface IntelFragment {
  _id: Id<"intelFragments">;
  _creationTime: number;
  personId: Id<"persons">;    // Indexed
  source: string;             // e.g., "linkedin", "twitter"
  dataType: string;           // e.g., "profile", "work_history"
  content: string;            // Raw intel text
  verified: boolean;          // Manual verification flag
  timestamp: number;          // Milliseconds since epoch
}

ActivityLog

interface ActivityLog {
  _id: Id<"activityLog">;
  _creationTime: number;
  type: "capture" | "identify" | "research" | "complete";
  message: string;
  timestamp: number;
  personId?: Id<"persons">;   // Optional link to person
  agentName?: string;         // Optional agent identifier
}

Backend Integration

Store intel fragments from research agents:
# backend/agents/orchestrator.py
class ResearchOrchestrator:
    async def on_agent_result(self, person_id: str, agent_name: str, snippets: list[str]):
        for snippet in snippets:
            await self.convex.mutation(
                "intel:create",
                personId=person_id,
                source=agent_name,
                dataType="profile",
                content=snippet,
                verified=False
            )

Real-Time Updates

Intel fragments automatically sync to all connected clients:
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';

function LiveIntelFeed({ personId }: { personId: Id<"persons"> }) {
  // Automatically re-renders when new intel arrives
  const intel = useQuery(api.intel.getByPerson, { personId });
  
  return (
    <div>
      <h3>Live Research Feed</h3>
      {intel?.map(fragment => (
        <div key={fragment._id} className="intel-fragment">
          <span className="source">{fragment.source}</span>
          <p>{fragment.content}</p>
          <time>{new Date(fragment.timestamp).toLocaleString()}</time>
        </div>
      ))}
    </div>
  );
}

Activity Log Types

TypeDescriptionExample Message
captureNew face captured”New face captured via glasses_stream”
identifyPerson identified”Identified: John Doe (94% confidence)“
researchResearch in progress”[LINKEDIN] New profile intel for Jane Doe”
completeResearch completed”Jane Doe: status → COMPLETE”

Filtering Intel by Source

function IntelBySource({ personId, source }: { personId: Id<"persons">; source: string }) {
  const allIntel = useQuery(api.intel.getByPerson, { personId });
  const filtered = allIntel?.filter(i => i.source === source);
  
  return (
    <div>
      <h3>{source.toUpperCase()} Intel ({filtered?.length})</h3>
      {filtered?.map(fragment => (
        <div key={fragment._id}>{fragment.content}</div>
      ))}
    </div>
  );
}

Verified vs Unverified Intel

function VerifiedIntel({ personId }: { personId: Id<"persons"> }) {
  const allIntel = useQuery(api.intel.getByPerson, { personId });
  const verified = allIntel?.filter(i => i.verified);
  const unverified = allIntel?.filter(i => !i.verified);
  
  return (
    <div>
      <section>
        <h4>Verified ({verified?.length})</h4>
        {verified?.map(i => <IntelCard key={i._id} intel={i} />)}
      </section>
      <section>
        <h4>Unverified ({unverified?.length})</h4>
        {unverified?.map(i => <IntelCard key={i._id} intel={i} />)}
      </section>
    </div>
  );
}

Performance Considerations

Use the by_person index for fast per-person queries
Limit recentActivity calls to dashboard views
Consider pagination for persons with 100+ intel fragments
Don’t store large documents (>100KB) in intel fragments
Activity log grows indefinitely (implement pruning if needed)

Best Practices

Create intel fragments as agents stream results
Use descriptive dataType values for filtering
Mark intel as verified after manual review
Include source URLs in content when available
Don’t duplicate intel (check existing fragments first)
Don’t store PII in unencrypted content fields

Build docs developers (and LLMs) love