Skip to main content
Data sovereignty and isolation is central to our architecture. Each private “tenant” is deployed in a dedicated infrastructure ensuring there is no comingling of client data. Ntropii separates control from data. Two distinct planes with strict isolation between them:
┌─────────────────────────────────────────────────────┐
│              Ntropii Workspace (you)                │
│                                                     │
│   - Tenant + entity registry                        │
│   - Workflow definitions and versions               │
│   - Deployment + schedule metadata                  │
│   - Run status / events (no payloads)               │
│   - API keys, organisation, users                   │
│                                                     │
│   Reached via:  CLI   |   MCP   |   SDK             │
└────────────────────────┬────────────────────────────┘

                command + status events only
                  (no financial data)

┌────────────────────────▼────────────────────────────┐
│             Ntropii Tenant (per client)             │
│                                                     │
│   - Runbook execution (Temporal workers)            │
│   - Data platform credentials                       │
│   - Document storage                                │
│   - Direct access to your warehouse                 │
│                                                     │
│   Bound to:  Managed Postgres  |  Snowflake  |       │
│              Microsoft Fabric                       │
└─────────────────────────────────────────────────────┘

The boundary

Ntropii Workspace is what you reach when you run ntro tenant create or call an MCP tool from an LLM. It holds metadata: who owns which tenant, which entities (SPVs, funds) belong to which tenant, which workflow versions have been pushed, which deployments are active, which scheduled runs are coming up. It does not hold any financial data. No journal lines, no trial balances, no extracted documents. Those never leave Ntropii Tenant. Ntropii Tenant is the runtime that actually executes your runbooks. One tenant binds to exactly one data platform — Ntropii’s managed Postgres (provisioned per tenant, isolated from every other tenant), a Snowflake account, or a Microsoft Fabric workspace. The credentials for the chosen platform live inside Ntropii Tenant; they never cross to Workspace. Every tenant gets its own isolated infrastructure regardless of which strategy it picks. The boundary is the tenant; what differs is who hosts the platform underneath. With managed Postgres, Ntropii hosts the database and isolates per tenant. With BYO (Snowflake, Microsoft Fabric), the customer hosts the platform and Ntropii holds only the credentials needed to read from it. When a workflow runs, it executes inside Ntropii Tenant against the chosen data platform. Status events (started, step completed, run finished) flow back to Workspace so you can monitor in the CLI / UI / MCP. The actual data — the documents, the trial balance, the journal entries — stays in the data platform.

Tenants and entities

A service provider (e.g. accounting firm, fund administrator, transfer agent, multi family office) or asset manager would signup as an organisation creating a workspace. “Fund Admin Workspace” is the organisation name in our example below who has a workspace. A tenant is a client of the workspace’s organisation — typically a fund. Acme Fund is a tenant. They map 1:1 to a data platform binding. An entity is a company, SPV, protected cell or incorporated cell within a tenant. A tenant can have many entities — each with its own schema in the underlying data platform, its own scheduled workflows, its own task history. “Acme Commercial SPV 1” is an entity belonging to the Acme Fund tenant, sharing Acme’s data platform but living in its own schema.
Fund Admin Workspace
└── Tenant: Acme Fund
    │   (data-platform: managed-postgres)
    ├── Entity: Acme Commercial SPV 1   → schema: acme_spv1
    ├── Entity: Acme Commercial SPV 2   → schema: acme_spv2
    └── Entity: Acme Infrastructure     → schema: acme_infra
Workflows are registered at the tenant level, deployed to Ntropii Tenant, and triggered against an entity for a specific period.

Why the split

Three reasons, all rooted in the regulated nature of fund operations:
Customer financial data — invoices, bank statements, GL entries, NAV history — never enters Ntropii Workspace. It stays in Ntropii Tenant. With managed Postgres that database is hosted by Ntropii but isolated to one tenant; with BYO (Snowflake, Microsoft Fabric) the data stays in the customer’s own warehouse under their own credentials and retention policies. If a customer’s compliance team requires that no fund data crosses a particular border, the BYO route deploys Ntropii Tenant in their own region (or their own cloud account) without changing the architecture.
Each tenant has its own runtime, its own credentials, and its own data platform binding. A bug in one tenant’s runbook can’t leak data to another tenant — there’s no shared execution surface. API keys are scoped to a tenant (the organisation is recorded only as provenance), so a leaked key affects exactly one client’s surface area.
The control plane has a clean audit trail of what was deployed when. The data plane has a clean audit trail of what was processed when. They join on workflow ID + run ID. An auditor reconstructing a NAV from six months ago can pull the runbook source from Workspace at that commit, the input documents from the customer’s data platform at that timestamp, and re-run the flow to verify the output bit-for-bit.

What this means for you

You’ll see this split in three concrete places:
  • ntro tenant create --data-platform <choice> declares the data-platform strategy. With managed-postgres, that’s all there is — Ntropii provisions and isolates the database. With snowflake or microsoft-fabric, the tenant binds to a config registered separately via ntro integration add, which is the moment Ntropii Tenant gets the customer credentials.
  • ntro workflow create --path ./runbooks/<slug>/ uploads the runbook bundle to Ntropii Tenant’s worker, where it can execute against the data platform.
  • ntro workflow create --entity <entity> (same command, with --entity) also creates a workflow binding so the runbook can be triggered for that specific entity.

Data lifecycle: from agent input to GL

Inside a tenant, data tightens through four stages on its way from a fuzzy agent submission to an immutable general-ledger entry. Each stage has its own type strictness, its own storage location, and its own reviewer. By the time a value reaches stage 4, every prior stage’s invariants are encoded in the type — you cannot post an unbalanced journal because the type system has already eliminated invalid states. This is the regulatory contract: “parse, don’t validate” is what makes a NAV reproducible from the source code plus its inputs, six months after the fact, bit-for-bit. JFSC, CSSF, FSRA, and Big-4 auditors all want the same thing — a paper trail showing exactly which checks ran when. The lifecycle below is what materialises that trail in code rather than policy.
A coding agent or MCP client submits a payload via ntro_task_data_ingest. The platform stores the raw data field as JSONB in ingest.submitted_records inside the tenant’s Postgres, alongside provenance (event id, source ref, received at, kind). Nobody type-checks the payload at this stage — agents send fuzzy strings, dicts, partial fields, locale-specific dates, whatever the source document looked like. The point is preserving exactly what was submitted so an auditor can reconcile back to the wire format.
A runbook activity reads the JSONB payload and model_validates it through a Pydantic boundary model declared with ntro.types.* types — ForgivingDate for fuzzy dates, eventually ForgivingMoney / Currency siblings as runbooks need them. These types coerce ISO strings, locale-tolerant strings, and natural language into typed values; on parse failure they degrade to None rather than raising. The activity then writes the typed values to a subledger (e.g. ledgers.expenses) with status=PENDING. This is the first rung where the data is typed; rows are still allowed to have NULLs because the next stage’s reviewer fills them in.
A human reviewer sees every PENDING row in a DATA_TABLE HITL step — typically a fund accountant during the period close. They correct mis-categorised vendors, fill in missing dates, flag ambiguous amounts, and approve the row. On approval the SDK flips status PENDING → APPROVED. Required fields are now present; lifecycle invariants hold. Nothing reaches the GL without passing through this gate.
A separate runbook step (or a downstream workflow) rolls up APPROVED subledger rows into balanced journal proposals and posts them to the customer’s general ledger — Xero, QBO, or whichever GL the tenant binds to. The GL provider rejects anything that doesn’t balance, doesn’t reference a valid CoA code, or duplicates an existing posting (idempotent by external ref). Once posted, the row is immutable; corrections are issued as offsetting journals, never as updates.
The point of the ladder is that the type carries proof of every check that has cleared so far. By the time a JournalLine is constructed, its existence is evidence that the agent submission parsed cleanly, the reviewer signed off, and the CoA code is valid. That’s the regulator’s audit trail rendered as types — not a separate compliance artefact, but the same code that runs in production. The next page walks through both setup steps.

Configure environment

Bind a data platform and set up your runbook repo.

API keys

Generate, scope, and rotate keys for CLI / MCP / SDK access.