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:
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:
Person this intel relates to.
Source identifier (e.g., "linkedin", "twitter", "exa", "google").
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
Raw intel content (text snippet, JSON string, or URL).
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
| Type | Description | Example Message |
|---|
capture | New face captured | ”New face captured via glasses_stream” |
identify | Person identified | ”Identified: John Doe (94% confidence)“ |
research | Research in progress | ”[LINKEDIN] New profile intel for Jane Doe” |
complete | Research 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>
);
}
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