Entities & Layered Context
Every business has a "thing" it works on. An agency has clients, a law firm has matters, a clinic has patients, a real-estate team has properties, an accountant has engagements, a sales team has accounts. Jet calls this an entity and lets installed packs name it.
Everything else — projects, context, memory, workflows — hangs off the entity. That's how Jet stays generic without being useless.
Entity types
The pack you install registers one or more entity types via the
workspace_entity_types table. An entity type has:
- slug —
client,matter,patient, ... - labelSingular / labelPlural — how the UI labels it (
Client/Clients) - icon — which icon the sidebar renders
- schema — which per-entity fields the create form shows
The marketing-agency pack's client schema, for example, defines
industry, monthlyAdSpend, targetRoas,
primaryPlatforms, contractStart, monthlyRetainerUsd.
Entity instances
An entity instance (the row in workspace_entities) represents one actual
client / matter / patient. Its fields column stores the JSON blob that
matches the type's schema. Entities also carry:
- owner_user_id — the primary relationship owner
- status —
activeorarchived(never hard-delete) - projects — scoped deliverables (campaigns, matters, cases, deals, engagements)
Layered context
Context is what's active in an agent session. Memory is what persists across sessions. Jet's context architecture has four layers; the first three are stored in the tenant's D1 and assembled by the context loader at every agent run, the fourth is runtime.
| Layer | Scope | Table |
|---|---|---|
| Institutional | Once per tenant — voice, SOPs, standards, case studies | context_institutional (singleton) |
| Entity | Per entity — overview, preferences, history | context_entity |
| Project | Per project — brief, scope, deadline, sources, format | context_project |
| Session | Per conversation — automatic, managed by ChatSession Durable Object | (runtime) |
How the loader assembles context
When an agent runs, services/contextLoader.ts builds a Markdown block from the
first three layers and prepends it to the system prompt. Each layer is labelled so the
model knows what it's looking at, and empty layers are skipped.
import { loadContext } from "./contextLoader";
const { markdown, hasInstitutional, hasEntity, hasProject } = await loadContext(db, {
tenantId,
entityId: "ent_acme",
projectId: "prj_q3_campaign",
});
// markdown looks like:
// ## Institutional context
// ### Voice
// Client-facing: confident, direct, data-first ...
//
// ## Client context — Acme Corp
// - **industry:** DTC e-commerce
// - **monthlyAdSpend:** 65000
// ### Overview
// Apex Roofing is a performance client onboarded ...
//
// ## Project context
// **Project:** Q3 Performance Report
// **Due:** 2026-04-25
// ### Brief
// Report period: July 1 – September 30 ...
API reference
Entities
GET /entities/types— list the entity types this tenant has (defined by installed packs)POST /entities/types— define a new entity type (or let a pack do it)GET /entities?type=client&status=active— list entitiesPOST /entities— create an entityGET /entities/:id,PUT /entities/:id,DELETE /entities/:id(archive) — CRUDGET /entities/:id/projects,POST /entities/:id/projects— scoped projects
Context
GET / PUT /context/institutional— tenant singletonGET / PUT /context/entity/:entityId— per-entity dossierGET / PUT /context/project/:projectId— per-project briefGET /context/preview?entityId=&projectId=— returns the exact Markdown block an agent would receive
Use /context/preview while authoring skills or testing agent behavior —
it's the fastest way to debug "why did the agent not know X?" questions.
Next steps
- Memory layer — what persists between sessions
- Packs — how entity types get installed
- Workflows — orchestrating agents with entity + project context