Skip to main content

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:
id
Id<'persons'>
required
Convex document ID.
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
string
required
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:
person_id
string
required
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:
name
string
required
Person’s full name.
photoUrl
string
required
URL of primary face photo.
confidence
number
required
Identification confidence score (0.0 to 1.0).
boardPosition
object
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:
id
Id<'persons'>
required
Person document ID.
status
string
required
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:
id
Id<'persons'>
required
Person document ID.
dossier
object
required
Complete dossier object.
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:
id
Id<'persons'>
required
Person document ID.
boardPosition
object
required
New board position.
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:
data
any
required
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:
person_id
string
required
Pipeline-generated person ID.
data
any
required
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

Build docs developers (and LLMs) love