Skip to main content
A workspace keeps your databases in one conversation without inviting them to share a toothbrush.

1. Motivation

Without one owner, schema setup happens in a migration script, provider clients live in global variables, the CLI chooses another config, and the MCP server quietly opens a second copy. Each piece works alone while the application loses one coherent identity. A HeavenBase workspace binds one stable id to canonical Entity classes, named Backend instances, field placement, row and query services, fixed metadata entities, requested Extension roots, and agent-facing tools. The owning Context supplies machine configuration, module resolution, and durable workspace registration. This matters because one logical row may span SQL, vector, search, graph, or file providers, but application code still needs one place to register, write, query, inspect, and explain it.

2. Create or Compatibly Reopen a Durable Workspace

HeavenBase("shop", ...) is idempotent at the durable identity. It creates and registers an absent workspace, or opens the existing compatible definition. It never activates the workspace implicitly. An explicit preset= or backends= value is a construction assertion. A conflict with the stored construction raises FileExistsError. With neither setting, an existing id reuses its persisted construction instead of reinterpreting today’s defaults. Use load() when absence should be an error:
HeavenBase.load("shop") requires a registered workspace and raises KeyError on a miss. Name-free load() checks the active selection, then the configured default; it never creates a workspace as a side effect.

3. Bind Isolation to a Context

Every Registry-backed lookup inside the workspace inherits this Context and its module resolver. Two Contexts may own distinct live workspaces with the same id. hb.DEFAULT_CONTEXT is only the convenience owner for unbound calls. The stable workspace id also derives its configuration layer: shop overlays heavenbase.shop on the base heavenbase config. Reopen a workspace after material configuration changes when already-built resources must observe the new snapshot.

4. Start with a Preset or Explicit Backends

The debug preset needs no external services:
Use an explicit map when deployment placement matters:
Backend keys such as main and vec are workspace-local names. Each type resolves a canonical backend_type record through the Context. Ready Backend instances use the same identity contract; after process restart, reconstruction needs compatible live instances or explicit replacement configuration.

5. Register Schema Before Row CRUD

register() establishes the canonical Entity class, resolves placement, ensures physical schema, and publishes MetaSchema rows. Ordinary upsert, set, delete, get, exists, count, ids, and rows operate only on an already-present canonical class. They never create schema state as a hidden side effect. Every row has a stable object_id. If a row omits it but provides name, HeavenBase derives a deterministic identifier from the Entity schema and that name.

6. Query Through One Logical Surface

Query transformations return new immutable hb.Query values. execute() recomputes and returns a detached ResultFrame; use explicit exits such as rows(), scalar(), or a typed dataframe export. The workspace resolves placement, registered operations, handlers, and provider execution. explain() reports the actual native, adapter, scan, fallback, or unsupported route instead of treating a module tag as proof.

7. Inspect Foundation Entities and Extensions

Catalog and MetaSchema are fixed, non-replaceable workspace foundation entities. Catalog describes concrete objects. MetaSchema describes canonical schemas, fields, placement, active Extensions, and safe Registry-backed metadata.
Capsule is the required Extension root. Toolkit and Prompt are packaged default roots; Agent, Memory, and Database are optional. The workspace stores requested optional roots and recomputes exact dependencies when it reopens.

8. Export a Reconstructive Shell, Not the Data

Workspace manifest version 2 stores one construction envelope, requested optional Extension roots, and user Entity schemas:
Configured Backends store their full replayable map. Live Backend objects store only the complete name-to-canonical-identifier signature; an identifier never fabricates credentials, endpoints, files, or clients. Manifests do not export stored rows or physical Backend data. Replay may replace a replayable construction with a complete preset= or backends= value, but it does not recursively merge partial Backend settings.

9. Use Detached Lifecycle Deliberately

A detached workspace is caller-owned: it is not registered, cannot be activated, and cannot be found by load(). Detached is a lifecycle policy, not a storage promise. It does not imply temporary or isolated data, and cleanup is not automatic. release() removes a durable workspace facade from its owning Context’s live lookup while preserving Backend data. drop() is the destructive physical-data operation. Retain the correct facade and call these methods explicitly.

10. Know the Coordination Limit

One live workspace serializes its public schema, CRUD, query, refresh, repair, release, and drop operations. Independent routed Backends still do not form a distributed transaction or cross-process serialization boundary. A failed multi-backend write can require explicit audit and convergence. Use ws.audit() for Catalog consistency and ws.repair(dry_run=True) before intentional repair. Use Query.explain() before promising provider-native behavior.

Summary

  • A workspace owns schema, placement, rows, routing, and attached domain APIs.
  • Durable construction, loading, activation, and detached lifecycle are separate decisions.
  • Manifests reconstruct the shell; Backends and applications own physical data guarantees.

Further Exploration

Related resources: