Getting started
Status: Fixture Preview. What you run locally is the same fixture rehearsal harness the rest of these docs describe. No real company reality is bound on your machine.
This page gets you from a fresh checkout to your first membrane crossing in about ten minutes.
What you will need
Section titled “What you will need”- Rust — stable toolchain (
rustup default stable). The workspace uses edition 2024 features; recent stable is fine. - Node.js — 20+ for the TypeScript SDK and the React workbench.
- git — to clone the repo.
- Optional: SurrealDB binary if you want to point the fixture at an external Surreal instance. Default uses an in-memory store.
Clone and build
Section titled “Clone and build”git clone <repo-url> gestalt-aioncd gestalt-aioncargo buildThe first build pulls a chunky dependency tree (axum, surrealdb, ed25519-dalek, etc.). Subsequent builds are fast.
Run the fixture cloud
Section titled “Run the fixture cloud”cargo run -p gestalt-cli -- cloud serve-fixture --addr 127.0.0.1:3011You should see something like:
gestalt cloud (fixture mode) listening on 127.0.0.1:3011production_admission: falsemembrane contract: 0.0.1-fixtureThe address 127.0.0.1:3011 matches the systemd staging unit; you can
choose any free port for local work.
Hit the observability endpoints
Section titled “Hit the observability endpoints”In another terminal:
curl -s http://127.0.0.1:3011/healthcurl -s http://127.0.0.1:3011/readycurl -s http://127.0.0.1:3011/versioncurl -s http://127.0.0.1:3011/metricsThese are unauthenticated probes and exist mostly for ops. They prove the server is up.
Get tenant context (your first membrane crossing)
Section titled “Get tenant context (your first membrane crossing)”Every authenticated call uses a bearer token. In fixture mode the token is fixed:
fixture-session-tokenCall tenant.self:
curl -s http://127.0.0.1:3011/v1/tenant/self \ -H "Authorization: Bearer fixture-session-token"You should see a response shaped like:
{ "operation": "tenant.self", "outcome": "verified", "body": { "tenant": "tenant_node:rheinwerk_calibration", "productionAdmission": false }, "receipt": { "ref": "receipt:fixture_tenant_self", "outcome": "verified", "reasons": ["tenant self resolved through fixture SDK fetch"], "fixture": true }}The fixture tenant tenant_node:rheinwerk_calibration is the only
tenant today. See
022 gap report
item 7.
Make your first proposal (intent precheck)
Section titled “Make your first proposal (intent precheck)”curl -s http://127.0.0.1:3011/v1/intents/precheck \ -H "Authorization: Bearer fixture-session-token" \ -H "content-type: application/json" \ -d '{ "tenant": "tenant_node:rheinwerk_calibration", "capability": "capability:issue_invoice_fixture_v1", "action": "issue", "evidence": [] }'You should see a structured refusal because no evidence was cited:
{ "operation": "intent.precheck", "outcome": "refused", "body": { "failedGate": "required_evidence_missing", "missingEvidence": ["invoice_payload"] }, "receipt": { "ref": "receipt:fixture_precheck_refusal", "outcome": "refused", "reasons": ["fixture precheck requires invoice_payload"], "fixture": true }}This is the membrane working correctly. A refusal in Gestalt is not an error — it is a structured finding about why an act could not enter reality. See reference/refusal-codes.md.
Use the TypeScript SDK
Section titled “Use the TypeScript SDK”Install:
cd sdk/tsnpm installnpm run buildA minimal client:
import { GestaltClient } from "@gestalt/sdk";
const client = new GestaltClient({ baseUrl: "http://127.0.0.1:3011", token: "fixture-session-token",});
const tenant = await client.tenantSelf();console.log(tenant);
const refusal = await client.precheckIntent({ tenant: "tenant_node:rheinwerk_calibration", capability: "capability:issue_invoice_fixture_v1", action: "issue", evidence: [],});console.log(refusal);Run with tsx or your runner of choice. See sdk/typescript.md.
Render the outcome
Section titled “Render the outcome”Every membrane response carries an outcome. A Koerper renders the
outcome shape; it does not coerce refusals into errors:
switch (response.outcome) { case "admitted": case "verified": case "executed": // success path: read response.body, store response.receipt.ref break; case "refused": // structured finding: surface response.body and response.receipt.reasons break; case "pending": // act needs higher signing or more evidence; persist the pending ref break; case "projected": // admitted into a projection, not into record break; case "queued": // accepted into the effect outbox (effect.intent) break; case "failed": // dispatch attempted but failed (effect.dispatch) break;}A non-2xx HTTP response is a network/transport error. A refused
outcome is a normal 2xx response with structured reasons — treat it as
data, not as an exception.
Use the SDK in pure fixture mode (no server needed)
Section titled “Use the SDK in pure fixture mode (no server needed)”The TS SDK ships a built-in fixture fetch that returns canned responses without any server. Useful for unit tests.
import { GestaltClient, createFixtureFetch } from "@gestalt/sdk";
const client = new GestaltClient({ baseUrl: "http://fixture", fetchImpl: createFixtureFetch(),});
const tenant = await client.tenantSelf();// { outcome: "verified", body: { tenant: "tenant_node:rheinwerk_calibration", ... } }See sdk/fixture-mode.md.
Use the Rust SDK
Section titled “Use the Rust SDK”The Rust SDK does not call HTTP today. It is a local fixture client that returns shape-correct JSON values, useful for embedding Gestalt-shaped reasoning into your own Rust binary.
use gestalt_sdk::{GestaltClient, IntentPrecheckRequest};
let client = GestaltClient::fixture();let response = client.precheck_intent(IntentPrecheckRequest { intent_kind: "invoice.issue".to_string(), actor_ref: "human_person:anna".to_string(), effect_class: "economic_action".to_string(), evidence_refs: vec!["evidence_bundle:invoice_payload".to_string()],});println!("{}", response);If you need to actually call the HTTP membrane from Rust, hit the
endpoints with reqwest directly using the contract at
contracts/gestalt-cloud-membrane.v0.json
as the spec. See sdk/rust.md.
Try the workbench CLI
Section titled “Try the workbench CLI”The CLI is a developer workbench, not an admin app. It crosses the membrane the same way every other vessel does.
# See the full command treecargo run -p gestalt-cli -- --help
# Walk the German invoice / payment / advisor scenario as a fixturecargo run -p gestalt-cli -- scenario march-close
# Inspect the membrane contractcargo run -p gestalt-cli -- cloud membrane-contract
# Self-test the fixture HTTP routescargo run -p gestalt-cli -- cloud selftest-routesSee cli/reference.md for the full command set.
Try the React workbench
Section titled “Try the React workbench”The React workbench is a browser surface for inspecting Gestalt crossings. It’s actively under development.
cd apps/react-workbenchnpm installnpm run dev# open http://127.0.0.1:5177Try Gestalt as MCP tools
Section titled “Try Gestalt as MCP tools”The MCP server exposes a curated set of inspection / simulation / proposal tools to LLM-driven agents and assistants. Production admission is blocked by manifest.
# See the manifestcat mcp/gestalt-mcp.manifest.json
# Run the server (Node)node mcp/server.mjsSee mcp/README.md.
What just happened — the membrane in one picture
Section titled “What just happened — the membrane in one picture”Every call you made above did the same thing under the hood:
your vessel (curl, SDK, CLI, workbench, MCP) | | intent envelope v membrane (HTTP) ── fixture bearer token validates session ── | v Gravity (structural admission) ── refuses, admits, or pends ── | v receipt back to youThe membrane is the line between what you are doing (Koerper) and
what becomes company reality (Geist). Today, no real company
reality is bound on your machine — every receipt carries fixture: true and every body has production_admission: false. Tomorrow, the
same calls will cross into authentic admission.
Where to read next
Section titled “Where to read next”- Concepts: the membrane — the model behind every call you just made.
- Concepts: Geist and Koerper — why the membrane exists at all.
- SDK: TypeScript — every client method.
- API reference — every membrane operation.
- Guide: German invoice / payment / advisor — the worked end-to-end fixture walk.
- Guide: building a Koerper — the discipline a Koerper must follow when it crosses the membrane.
- Operations: local fixture — running the fixture beyond a one-shot demo.
Troubleshooting
Section titled “Troubleshooting”cargo build fails on dependency resolution
Section titled “cargo build fails on dependency resolution”The workspace pins SurrealDB 3.0.5 and other versions. Make sure your
toolchain is recent. rustup update stable usually fixes it.
cloud serve-fixture exits immediately
Section titled “cloud serve-fixture exits immediately”Another process may already be on the port. Try
--addr 127.0.0.1:3012.
My SDK call returns 401
Section titled “My SDK call returns 401”The fixture bearer token is exactly fixture-session-token. Other
fixture tokens used in tests:
fixture-session-token-secondary valid alternateexpired-fixture-session-token refuses with session_expiredwrong-scope-fixture-session-token refuses with scope_mismatchtampered-fixture-session-token refuses with token_tamperedMy call returns production_admission: false
Section titled “My call returns production_admission: false”That is correct. No call returns anything else today. See the 022 gap report.
My call returns a refusal I do not understand
Section titled “My call returns a refusal I do not understand”See reference/refusal-codes.md. The fixture deliberately surfaces rich refusals because the refusal discipline is the load-bearing part of Gestalt.