Overview
The persons module manages identified person records, dossiers, and status tracking with real-time synchronization.
Import path:
import { api } from '@/convex/_generated/api';
Queries
listAll
Retrieve all person records.
Type signature:
listAll: () => Promise<Person[]>
Usage:
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';
function PersonsList() {
const persons = useQuery(api.persons.listAll);
return (
<div>
{persons?.map(person => (
<PersonCard key={person._id} person={person} />
))}
</div>
);
}
getById
Get a single person by Convex document ID.
Type signature:
getById: (args: { id: Id<"persons"> }) => Promise<Person | null>
Parameters:
Usage:
const person = useQuery(api.persons.getById, { id: personId });
if (!person) return <div>Person not found</div>;
getByStatus
Filter persons by processing status.
Type signature:
getByStatus: (args: {
status: "identified" | "researching" | "synthesizing" | "complete"
}) => Promise<Person[]>
Parameters:
Status filter:
identified - Face identified, research not started
researching - Research agents actively gathering data
synthesizing - Dossier generation in progress
complete - Full dossier available
Usage:
const researching = useQuery(api.persons.getByStatus, { status: 'researching' });
return (
<div>
<h2>Currently Researching ({researching?.length})</h2>
{researching?.map(p => <PersonCard key={p._id} person={p} />)}
</div>
);
get
Backend-compatible get by pipeline person_id.
Type signature:
get: (args: { person_id: string }) => Promise<Person | null>
Parameters:
Pipeline-generated person ID (not Convex _id).
Usage (Backend):
person = await convex.query("persons:get", person_id="person_abc123")
Mutations
create
Create a new person record.
Type signature:
create: (args: {
name: string;
photoUrl: string;
confidence: number;
boardPosition?: { x: number; y: number };
}) => Promise<Id<"persons">>
Parameters:
URL of primary face photo.
Identification confidence score (0.0 to 1.0).
Optional position on corkboard UI. Auto-generated if omitted.
Returns:
Convex document ID of the created person.
Usage:
import { useMutation } from 'convex/react';
import { api } from '@/convex/_generated/api';
function CreatePerson() {
const createPerson = useMutation(api.persons.create);
const handleCreate = async () => {
const personId = await createPerson({
name: 'John Doe',
photoUrl: 'https://example.com/john.jpg',
confidence: 0.92,
boardPosition: { x: 200, y: 300 }
});
console.log('Person created:', personId);
};
return <button onClick={handleCreate}>Create Person</button>;
}
Side effects:
- Sets initial status to
"identified"
- Records
createdAt and updatedAt timestamps
- Creates activity log entry
- Auto-generates
boardPosition if not provided
updateStatus
Update person processing status.
Type signature:
updateStatus: (args: {
id: Id<"persons">;
status: "identified" | "researching" | "synthesizing" | "complete";
}) => Promise<void>
Parameters:
New status (see status values above).
Usage:
const updateStatus = useMutation(api.persons.updateStatus);
await updateStatus({
id: personId,
status: 'researching'
});
Side effects:
- Updates
updatedAt timestamp
- Creates activity log entry
updateDossier
Update person dossier with research results.
Type signature:
updateDossier: (args: {
id: Id<"persons">;
dossier: Dossier;
}) => Promise<void>
Parameters:
Complete dossier object.
Work history array (each item: {role, company, period?})
Education array (each item: {school, degree?})
Social links: {linkedin?, twitter?, instagram?, github?, website?}
Array of notable achievements
Array of conversation starters
Usage:
const updateDossier = useMutation(api.persons.updateDossier);
await updateDossier({
id: personId,
dossier: {
summary: 'Senior engineer at TechCorp...',
title: 'Senior Software Engineer',
company: 'TechCorp',
workHistory: [
{ role: 'Senior Engineer', company: 'TechCorp', period: '2020-Present' }
],
education: [
{ school: 'MIT', degree: 'BS Computer Science' }
],
socialProfiles: {
linkedin: 'https://linkedin.com/in/johndoe',
github: 'https://github.com/johndoe'
},
notableActivity: ['Keynote at TechConf 2023'],
conversationHooks: ['Ask about Kubernetes contributions'],
riskFlags: []
}
});
Side effects:
- Automatically sets status to
"complete"
- Updates
updatedAt timestamp
updatePosition
Update person’s position on the corkboard.
Type signature:
updatePosition: (args: {
id: Id<"persons">;
boardPosition: { x: number; y: number };
}) => Promise<void>
Parameters:
Usage:
const updatePosition = useMutation(api.persons.updatePosition);
// Drag and drop handler
const handleDragEnd = async (personId: Id<"persons">, x: number, y: number) => {
await updatePosition({ id: personId, boardPosition: { x, y } });
};
store
Backend-compatible generic store (creates person from pipeline data).
Type signature:
store: (args: { data: any }) => Promise<Id<"persons">>
Parameters:
Person data from pipeline. Expected fields:
name - Person’s name
photoUrl - Photo URL
confidence - Identification confidence (0.0-1.0)
Usage (Backend):
person_id = await convex.mutation(
"persons:store",
data={
"name": "Jane Doe",
"photoUrl": "https://...",
"confidence": 0.94
}
)
update
Backend-compatible generic update (patches person by person_id lookup).
Type signature:
update: (args: {
person_id: string;
data: any;
}) => Promise<void>
Parameters:
Pipeline-generated person ID.
Update data (status, summary, dossier, etc.).
Usage (Backend):
await convex.mutation(
"persons:update",
person_id="person_abc123",
data={
"status": "enriched",
"summary": "Senior engineer...",
"dossier": {...}
}
)
Behavior:
- Automatically maps pipeline statuses to Convex schema statuses
- Strips
null values from social profiles (Convex v.optional rejects null)
- Silently skips if person not found
deleteAll
Wipe all persons and related data (admin/debug only).
Type signature:
deleteAll: () => Promise<{ deleted: number }>
Usage:
const deleteAll = useMutation(api.persons.deleteAll);
const handleReset = async () => {
if (confirm('Delete all persons?')) {
const result = await deleteAll();
console.log(`Deleted ${result.deleted} persons`);
}
};
Side effects:
- Deletes all person records
- Deletes all activity log entries
- Deletes all intel fragments
- Deletes all connections
Schema
interface Person {
_id: Id<"persons">;
_creationTime: number;
name: string;
photoUrl: string;
confidence: number;
status: "identified" | "researching" | "synthesizing" | "complete";
summary?: string;
occupation?: string;
organization?: string;
dossier?: Dossier;
boardPosition: { x: number; y: number };
createdAt: number;
updatedAt: number;
}
interface Dossier {
summary: string;
title?: string;
company?: string;
workHistory: Array<{ role: string; company: string; period?: string }>;
education: Array<{ school: string; degree?: string }>;
socialProfiles: {
linkedin?: string;
twitter?: string;
instagram?: string;
github?: string;
website?: string;
};
notableActivity: string[];
conversationHooks: string[];
riskFlags: string[];
}
Real-Time Subscriptions
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';
function PersonCard({ personId }: { personId: Id<"persons"> }) {
// Automatically re-renders when person data changes
const person = useQuery(api.persons.getById, { id: personId });
if (!person) return null;
return (
<div>
<h3>{person.name}</h3>
<p>Status: {person.status}</p>
{person.status === 'complete' && person.dossier && (
<DossierView dossier={person.dossier} />
)}
</div>
);
}
Best Practices
Use getById for single-person views
Use getByStatus for filtered lists (e.g., “In Progress”)
Update status as pipeline progresses for real-time UX
Call updateDossier once when synthesis completes
Don’t call update with partial dossiers (overwrite entire dossier)
Avoid calling deleteAll in production