Entity System
Schema/behavior/UI split, three authoring modes, field builders, and the entity type lifecycle.
Overview
Entities are the fundamental data unit in the Sprinter Platform. Every record -- a deal, a company, a task, a document -- is an entity with a type. Entity types are defined by three layers:
- Schema -- JSON Schema defining the fields and their types
- Behavior -- Field configs controlling extraction, dependencies, and agent assignment
- UI -- Optional custom components for rendering (falls back to auto-rendering)
Entity types live in the database, not in code. The defineEntityType builder is used for seeding and module definitions. At runtime, the platform reads schemas from the database and renders generically.
Three Authoring Modes
1. Code-first (modules and seeds)
Use defineEntityType and field builders in TypeScript:
import { defineEntityType, field } from "@sprinterai/core";
const company = defineEntityType({
slug: "company",
name: "Company",
schema: {
type: "object",
properties: {
name: field.text({ title: "Company Name" }),
sector: field.select({
title: "Sector",
options: ["SaaS", "Fintech", "Healthcare"],
}),
revenue: field.currency({ title: "Revenue" }),
headcount: field.integer({ title: "Headcount", minimum: 1 }),
},
required: ["name"],
},
});2. Admin UI
Create and edit entity types through the Admin panel. The schema editor provides a visual interface for adding fields, configuring extraction, and setting up scoring criteria.
3. Agent-driven
AI agents with admin permissions can create and modify entity types using the createEntityType, updateEntityTypeSchema, and updateFieldConfig tools.
Entity Type Record
The full entity type record stored in the database:
interface EntityTypeRecord {
id: string;
tenant_id: string;
slug: string;
name: string;
name_singular: string | null;
description: string | null;
icon: string | null;
color: string | null;
json_schema: EntityJsonSchema;
behavior_json: EntityTypeBehaviorJson | null;
ui_json: EntityTypeUiJson | null;
config: EntityTypeConfig;
is_visible: boolean;
sort_order: number;
created_at: string;
updated_at: string;
}Entity Record
interface EntityRecord {
id: string;
tenant_id: string;
entity_type_id: string;
entity_type_slug: string; // denormalized for filtering
name: string;
metadata: EntityMetadata;
tags: string[];
score: number | null;
status: string | null;
created_by: string | null;
created_at: string;
updated_at: string;
}The metadata JSONB field stores the entity's field values, keyed by the property names from the JSON Schema.
Field Configuration
Beyond the JSON Schema property definition, each field can have extraction configuration:
interface FieldConfig {
key: string;
extraction?: {
agentSlug: string; // which agent populates this field
prompt?: string; // extraction instructions
source?: ExtractionSource;
dependsOn?: string[]; // field dependencies (DAG edges)
};
scoring?: {
criteriaSetSlug: string;
weight: number;
};
}This is what powers the core loop: fields declare which agent should populate them, what dependencies exist, and how they contribute to scoring.
Entity Store Interface
interface EntityStore {
getEntityType(slugOrId: string): Promise<EntityTypeRecord | null>;
listEntityTypes(): Promise<EntityTypeRecord[]>;
getEntity(id: string): Promise<EntityRecord | null>;
searchEntities(query: string, options?: {
typeSlug?: string;
tag?: string;
limit?: number;
}): Promise<EntityRecord[]>;
createEntity(input: CreateEntityInput): Promise<EntityRecord>;
updateEntity(id: string, input: UpdateEntityInput): Promise<EntityRecord>;
deleteEntity(id: string): Promise<void>;
createRelation(fromId: string, toId: string, type: string): Promise<void>;
getEntityCounts(tenantId: string): Promise<Record<string, number>>;
}Relations
Entities can be related to each other through typed relations:
import { relation } from "@sprinterai/core";
// In entity type config
const config = {
relations: [
relation("portfolio-company", {
targetTypeSlug: "company",
label: "Portfolio Company",
inverse: "owned-by",
}),
],
};Value Locking
Entity fields can be locked to prevent extraction from overwriting manual edits:
// entity.metadata.lockedFields = ["revenue", "sector"]
// Locked fields are skipped during extraction workflow runsEntity Visibility and Sharing
import { ENTITY_VISIBILITY, ENTITY_SHARE_ROLES } from "@sprinterai/core";
// Visibility: "private" | "team" | "public"
// Share roles: "viewer" | "editor" | "admin"AI Tools for Entities
The platform provides 8 built-in entity tools:
| Tool | Permission | Description |
|---|---|---|
searchEntities | entities.team.read | Search across all entity types |
getEntity | entities.team.read | Get a single entity by ID |
createEntity | entities.team.create | Create a new entity |
updateEntity | entities.team.update | Update entity fields |
deleteEntity | entities.team.delete | Delete an entity |
createRelation | entities.team.update | Create a relation between entities |
listEntityTypes | entities.team.read | List all entity types (supports detailed: true) |
getEntityStats | entities.team.read | Get entity counts per type |