Skip to main content
Entities say what the data is before anyone asks where it lives.

1. Define a Logical Shape

Entities are Python classes that extend hb.Entity. Use hb.field(...) when you want metadata, defaults, storage placement, or compute Hooks; use annotations for the shortest plain-field form.
import heavenbase as hb


class Document(hb.Entity):
    title = hb.field(hb.ShortText).desc("Display title")
    body = hb.field(hb.LongText)
    tags = hb.field(hb.Array[hb.ShortText]).default([])
    embedding = hb.field(hb.Vector[2])


print(Document.schema().entity_id)
The class above creates a logical entity named document. HeavenBase also injects object_id and name when you do not define your own object_id.

2. Choose Logical Types

Logical types describe meaning. Backends decide how those meanings are physically stored.
TypeUse it for
IdentifierStable IDs and slugs.
ShortText, MediumText, LongTextLabels, descriptions, and full documents.
Integer, Float, BooleanScalar values.
Categorical([...])String or integer values limited to known choices.
Timestamp(unit="ms"), Date()Instants and calendar dates.
Array[...]Lists with an optional item type.
Vector[dim]Embeddings and other numeric vectors.
JsonJSON-compatible objects.
HyperGRepeatable relation-like records.
ArtifactBinary payloads.
HeavenBase does not expose separate Datetime or Interval logical types yet. Use Timestamp for instants, Date for calendar dates, and a numeric field with a unit in its description for durations.

3. Understand Identity

Every entity row has exactly one user-facing object_id. If you omit it, HeavenBase uses the row’s name to derive a deterministic ID.
ws = hb.HeavenBase("core-entities", preset="debug")
ws.register(Document)

first_id = ws.upsert(
    Document,
    {
        "name": "Agent guide",
        "title": "Agent guide",
        "body": "Use Catalog for objects and MetaSchema for structure.",
        "tags": ["docs"],
        "embedding": [1.0, 0.0],
    },
)

second_id = ws.upsert(
    Document,
    {
        "name": "Agent guide",
        "title": "Agent guide",
        "body": "Updated body.",
        "tags": ["docs"],
        "embedding": [1.0, 0.0],
    },
)

print(first_id == second_id)
Define object_id yourself only when a different natural key should drive identity.
from heavenbase.utils import hash_id


def stock_id(sku: str, warehouse: str) -> str:
    return hash_id("StockItem", sku, warehouse)


class StockItem(hb.Entity):
    object_id = hb.field(hb.Identifier).compute(stock_id, inputs=["sku", "warehouse"])
    sku = hb.field(hb.ShortText)
    warehouse = hb.field(hb.ShortText)
    quantity = hb.field(hb.Integer).default(0)


ws.register(StockItem)
stock_id_value = ws.upsert(StockItem, {"sku": "SKU-001", "warehouse": "east"})
print(ws.get(stock_id_value, entity=StockItem)["quantity"])

4. Add Compute Hooks

Computed fields run when rows are written. Query-compute Hooks run when a query value needs to be normalized before dispatch. The common case is accepting a text query for a vector field.
def embed_text(text: str) -> list[float]:
    return [1.0, 0.0] if "agent" in text.lower() else [0.0, 1.0]


class Issue(hb.Entity):
    title = hb.field(hb.ShortText)
    summary = hb.field(hb.LongText)
    emb = (
        hb.field(hb.Vector[2])
        .compute(embed_text, inputs=["summary"])
        .query_compute(embed_text)
    )


ws.register(Issue)
ws.upsert(Issue, {"object_id": "i1", "title": "Agent memory", "summary": "Design agent memory around search."})

frame = ws.query(Issue).near(Issue.emb, "agent search", top_k=1).select("title", "score").execute()
print(frame.rows()[0]["title"])

5. Compile from JSON

Agents and external clients can define entities with JSON-compatible schema dictionaries.
Product = hb.Entity.from_schema(
    {
        "entity_id": "product",
        "name": "Product",
        "fields": {
            "name": "ShortText",
            "price": "Float",
            "status": {
                "type": {
                    "type": "categorical",
                    "values": ["draft", "active"],
                },
            },
            "created_at": {"type": {"type": "timestamp", "unit": "ms"}},
            "available_on": "Date",
        },
    }
)

print(Product.schema().entity_id)
The generated class behaves like a normal hb.Entity subclass and can be passed to ws.register(...), ws.upsert(...), and ws.query(...).

Further Exploration

Related resources:
  • Workspace - Register entities in an application boundary
  • Routing - Store fields on selected backends
  • Query - Filter, search, and inspect entity rows
  • Catalog - Discover concrete objects after writes