Skip to content

Concept: The membrane

Status: fixture-rehearsed. The membrane is the contract contracts/gestalt-cloud-membrane.v0.json served by the Axum runtime in crates/gestalt-cloud. Production admission across the membrane is gated.

Gestalt’s central architectural decision is a line called the membrane. On one side of the line is mutable work. On the other side is governed company reality. Every crossing is explicit, typed, and produces a receipt.

Koerper membrane Geist
(mutable) (intent envelope (governed)
in, receipt out)
UI state standing
drafts atoms
local tables realities
imports capabilities
agent scratchpads ─── intent ───> evidence bundles
generated workflows effects
caches closure surfaces
AI scratchpads <─── receipt ─── tensions
fixtures receipts

The membrane is not an API convenience. It is the place where ordinary computation asks to become consequential.

Most software does not have one. A typical app calls a function, the function writes a row, the row is now company truth. Gestalt refuses this because it makes a single layer responsible for two incompatible jobs:

  • navigation (showing operators paths through possibility), and
  • admission (binding a fact into company history).

Navigation wants to be opinionated, fast-moving, replaceable, and context-rich. Admission wants to be slow-moving, narrow, signed, and durable. When they share a layer, admission gets corrupted by navigation’s velocity and navigation gets choked by admission’s ceremony.

Gestalt splits them. The Koerper does navigation. The Geist does admission. The membrane is where they talk.

The thing that crosses is an intent envelope. It carries:

who is asking actor ref
under what surface vessel id + signing posture
for which company subject ref
in which reality record / projected:<id>
under which capability capability ref
with which content payload conforming to capability schema
citing which evidence evidence bundle refs
expecting which effects declared effect intent
with which signing posture how the vessel is willing to commit

This shape is consistent across every operation in the membrane contract. Operations vary in their bodies and outcomes; the envelope discipline is universal.

Every crossing returns a MembraneResponse:

interface MembraneResponse<T> {
operation: string; // "intent.commit", "reality.fork", ...
outcome: MembraneOutcome; // "admitted" | "refused" | "pending" | ...
body: T; // operation-specific payload
receipt?: MembraneReceipt;
}

The outcome is one of:

admitted serious atom committed
refused structured refusal
pending awaiting signing / evidence / advisor
projected admitted into a projected reality only
verified a non-mutating call (probe, query, verify) succeeded
queued accepted into a durable outbox
executed dispatched
failed dispatched but failed (idempotent record)

See reference/outcomes.md.

The receipt is shape-correct on every crossing. Today it is not cryptographically signed against canonical bytes (see 022 gap report item 17), but the discipline of “every crossing leaves a verifiable receipt” is enforced. See receipts-and-proofs.md.

The membrane is structured to refuse, not to forgive. A refusal is not an error. It is a structured finding about why an act could not enter reality.

A refusal carries:

failed_gate which structural check failed
capability_ref which capability was being cited
source_atoms what was being cited as cause
locus where in the envelope it failed
reason human-readable code
remedy_hint what could fix it
required_evidence what evidence would unblock it
required_intervention what professional act would unblock
possible_projection_path could this work in a projection
whether_failure_is_recordable does the company need to remember

Some failures are themselves company reality: an employee tried to bind a contract without standing, an agent attempted a risky production-continuity message. These get recorded. See reference/refusal-codes.md.

Open SDK boundary vs closed runtime boundary

Section titled “Open SDK boundary vs closed runtime boundary”

The membrane contract is explicit about who owns what.

Open to the SDK:

typed request envelopes
typed response envelopes
local receipt verification
fixture simulation
proof disclosure request construction

Closed — owned by the cloud runtime:

production admission
tenant key custody
authority package activation
pendulum evaluator dispatch
proof issuance
operator audit policy

Forbidden — no surface ever touches:

raw database access
private signing keys
cross-tenant graph traversal
production package mutation
unscoped proof bundle disclosure

These boundaries are part of the contract, not folklore. They show up literally in contracts/gestalt-cloud-membrane.v0.json.

The thing on the Koerper side of the membrane is called a vessel. A vessel may be:

  • a CLI session,
  • a native desktop app (e.g. macos-workbench),
  • a browser surface (e.g. react-workbench),
  • an MCP tool wired into an LLM,
  • an HSM-backed signer,
  • a hosted operator delegate (for shop-origin actions).

Each vessel has a declared signing posture:

dev_full_signing dev key against fixture substrate
local_fixture_signing fixture key, fixture substrate
proposal_only cannot sign; can only propose
paired_native_signing proposes via app; signs via paired native vessel
hsm_backed_signing signs through HSM
read_only can verify and inspect; cannot propose

Gravity uses the posture to decide whether the vessel can complete a serious act or only propose one. The same command from the same actor can produce a committed atom from one vessel and only a pending action from another.

This means: the CLI is not a backdoor. It crosses the membrane the same way a browser or a Steuerberater console does.

If a vessel cannot complete an act, the membrane does not error. It creates a pending action:

intent_envelope the original envelope
required_standing what standing the act needs
required_evidence what evidence is missing
required_channels how it can be completed (paired vessel, HSM)
expires_at deadline
origin_vessel who proposed
correlation_id ties the pending to its eventual completion
preview what would have happened
gravity_precheck which gates passed and which did not

A separate signing vessel — a phone, a hardware key, a paired native app — can later complete the pending action. The completion produces either an admitted atom or a refusal, citing the original origin.

This is how Gestalt avoids the worst dark pattern of governance software: silently downgrading a serious act to a casual one because the user happened to click from the wrong device.

A dry run asks: would this proposal pass Gravity against today’s record reality? It returns a structured evaluation. It does not bind anything.

A projection says: create a bounded possible worldline in which proposed atoms may have effects, closure surfaces, tensions, fibers, and receipts. A projection is governed; it is not a “test mode” or a “draft folder.”

Both are first-class. They are not the same. See realities.md.

Every serious crossing leaves a receipt. The CLI prints them aggressively. The SDK exposes them in MembraneResponse.receipt. The verification path is supposed to let any downstream party (auditor, counterparty, advisor) confirm what happened.

Today, receipt verification is shape-only — it checks the receipt fields are present and the outcome is from the known set. Real cryptographic verification against the runtime signer is on the roadmap. See receipts-and-proofs.md and 022 item 17.

In the contract:

{
"operation": "intent.commit",
"method": "POST",
"path": "/v1/intents/commit",
"runtime_owner": "cloud_geist",
"sdk_role": "submit standing-bound intent",
"request_record": "CloudIntentCommitRequest",
"responses": ["atom", "refusal", "pending_action", "receipt"]
}

In the TypeScript SDK:

const response = await client.precheckIntent({
tenant: "tenant_node:rheinwerk_calibration",
capability: "capability:issue_invoice_fixture_v1",
action: "issue",
evidence: ["evidence_bundle:invoice_payload"],
});
if (response.outcome === "refused") {
console.log("refused:", response.body.failedGate);
} else if (response.outcome === "admitted") {
console.log("atom committed:", response.body.atomRef);
}

In the Rust SDK (today: local fixture client):

let client = GestaltClient::fixture();
let response = client.precheck_intent(IntentPrecheckRequest {
intent_kind: "invoice.issue".into(),
actor_ref: "human_person:anna".into(),
effect_class: "economic_action".into(),
evidence_refs: vec!["evidence_bundle:invoice_payload".into()],
});
  • Not an audit log. An audit log records “X happened.” Gestalt records “X became admissible under standing S, evidence E, capability C, in reality R, citing prior atoms P, with effects F, signed by signer K.”
  • Not a workflow engine. Workflows live in Koerpers. The membrane doesn’t sequence steps; it admits acts.
  • Not a permission system. Permissions ask “can this user call this function?” The membrane asks “can this actor, under this mandate, in this situation, citing these facts, with this evidence, cause this act to become company reality?”
  • Not an event bus. Events are emitted from the Geist (effects, closure-surface changes), but the membrane is not a pub/sub.