Skip to content
Concepts

Domain Model

LeftFold is built on three concepts: aggregates, events, and traits. Everything else — agents, the mailbox, the query engine — follows from these.

Overview

Instead of storing content in rows that get overwritten, LeftFold records every change as an immutable event. Events belong to aggregates — domain objects like articles, people, or recipes. Aggregates declare traits that determine what AI agents do with them.

This architecture means full audit history, automatic AI enrichment, and custom content types that work instantly — no migrations, no schema changes, no agent reconfiguration.


Aggregates

An aggregate is a domain object with a distinct lifecycle — an Article, a Person, a Recipe, a Brand. Each aggregate has:

  • A type — what kind of thing it is (e.g. article, person)
  • An event stream — its complete history of changes
  • A set of traits — capabilities that determine how agents interact with it

You never read or write aggregates directly. You append events to record changes and fold the event stream to compute current state. See Event Sourcing for the full query model.


Events

An event is an immutable fact — something that happened. article.created, article.published, person.mentioned. Events are always:

  • Past-tense — describes what happened, not what should happen
  • Typed — follows the aggregate_type.action naming convention
  • Immutable — once recorded, never modified or deleted
  • Attributed — records who did it (user, agent, or system) and when

Event structure

{
  "event_id": "f47ac10b-...",
  "aggregate_id": "a1b2c3d4-...",
  "aggregate_type": "article",
  "event_type": "article.created",
  "principal_type": "user",
  "principal_id": "u1s2e3r-...",
  "payload": {
    "title": "ACL Recovery Timeline",
    "body": "# Introduction\n\nThe anterior cruciate ligament...",
    "slug": "acl-recovery-timeline",
    "tags": ["sports", "rehabilitation"]
  },
  "sequence_number": 1
}

Every aggregate type defines at minimum three events: .created, .updated, and .archived. Content types add .published, .enriched, and others.


Traits

Traits are capabilities an aggregate declares. They determine which agents enrich it. An article with the content-bearing trait automatically gets a summary, entity extraction, and vector embedding. A custom aggregate with the same trait gets the same enrichment — no configuration needed.

TraitDescriptionSubscribing agents
content-bearingContains long-form text suitable for NLPSummarizer, Embedder, Entity Extractor
media-bearingHas an associated binary file (image, video, audio, document)Kind Detector, Image Analyzer
entity-likeA named entity that accumulates mentions from contentEntity Extractor, Profile Builder
publishableHas a public-facing state (draft → published)Structured Data Generator, Linker
time-boundHas a temporal lifecycle (scheduled, occurs, past)Reserved for future agents
operationalInfrastructure aggregate — agents ignore its eventsNone (exclusion sentinel)

Traits decouple agents from aggregate types. When you create a custom Recipe aggregate with content-bearing and publishable traits, the summarizer, entity extractor, structured data generator, and linker all fire automatically — the same agents that handle articles.


Seeded Aggregates

Every workspace ships with 13 built-in aggregate types, organized into four categories:

Content types

TypeTraitsDescription
articlecontent-bearing, publishableLong-form text content with title, body, slug, tags
imagemedia-bearing, content-bearingImages with alt text, captions, dimensions
videomedia-bearing, content-bearingVideo files with duration, title, description
audiomedia-bearing, content-bearingAudio files with duration, title, description
documentmedia-bearing, content-bearingPDFs, Word docs, spreadsheets

Entity types

TypeTraitsDescription
personentity-likePeople referenced in content — name, role, bio
placeentity-likeLocations — address, coordinates, description
organizationentity-likeCompanies, institutions, teams

System types

TypeTraitsDescription
brandoperationalVoice, glossary, taxonomy, and content guardrails for agents
workspaceoperationalTenant boundary — members, settings
projectoperationalContent grouping within a workspace

Workflow types

TypeTraitsDescription
tasksupervisoryHuman-in-the-loop workflow — A2A-aligned lifecycle with threaded messages
artifactreviewableAgent-produced output — persists in the artifact library beyond task completion

Custom Aggregates

Beyond the 13 built-in types, you can define your own. Event storming is the primary way to do this: describe your business in plain language, and LeftFold's AI proposes aggregate types with appropriate traits.

How event storming works

  1. Describe your domain — "I run a physiotherapy clinic with four practitioners. We treat sports injuries and do post-surgical rehab."
  2. Review proposals — AI proposes 2-7 custom aggregate types (e.g. Practitioner, Treatment, Condition) with traits and field definitions
  3. Accept, edit, or reject — each proposal individually
  4. Immediately available — accepted types are queryable, creatable, and enriched by agents across all surfaces (MCP, CLI, SDK, API, dashboard)

Event storming is available after signup (onboarding), and can be re-run at any time from settings or via LeftFold storming start in the CLI. No migration needed — the fold-based query engine handles new types instantly.

Programmatic registration

You can also register aggregate types directly via the MCP tool or API:

// Via MCP tool: aggregate.define
{
  "type_name": "treatment",
  "display_name": "Treatment",
  "plural_name": "Treatments",
  "traits": ["content-bearing", "publishable"],
  "fields": [
    { "name": "name", "type": "string", "required": true },
    { "name": "description", "type": "text" },
    { "name": "duration_minutes", "type": "number" }
  ]
}

Knowledge Graph

Every aggregate automatically becomes a node in the workspace's knowledge graph. When content mentions entities — people, places, organizations — edges are created between them.

  • Nodes — one per aggregate instance, with traits and a semantic embedding (1536-dimensional vector)
  • Edges — created from .mentioned events, reference fields, and explicit relationships
  • Semantic search — query the graph by meaning, not just keywords, via pgvector

The entity extractor agent automatically creates mention edges when it finds people, places, or organizations in content. You can also create explicit relationships via the aggregate.relate MCP tool.