Skip to content

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.

  • 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.
Terminal window
git clone <repo-url> gestalt-aion
cd gestalt-aion
cargo build

The first build pulls a chunky dependency tree (axum, surrealdb, ed25519-dalek, etc.). Subsequent builds are fast.

Terminal window
cargo run -p gestalt-cli -- cloud serve-fixture --addr 127.0.0.1:3011

You should see something like:

gestalt cloud (fixture mode) listening on 127.0.0.1:3011
production_admission: false
membrane contract: 0.0.1-fixture

The address 127.0.0.1:3011 matches the systemd staging unit; you can choose any free port for local work.

In another terminal:

Terminal window
curl -s http://127.0.0.1:3011/health
curl -s http://127.0.0.1:3011/ready
curl -s http://127.0.0.1:3011/version
curl -s http://127.0.0.1:3011/metrics

These 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-token

Call tenant.self:

Terminal window
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)”
Terminal window
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.

Install:

Terminal window
cd sdk/ts
npm install
npm run build

A 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.

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.

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.

The CLI is a developer workbench, not an admin app. It crosses the membrane the same way every other vessel does.

Terminal window
# See the full command tree
cargo run -p gestalt-cli -- --help
# Walk the German invoice / payment / advisor scenario as a fixture
cargo run -p gestalt-cli -- scenario march-close
# Inspect the membrane contract
cargo run -p gestalt-cli -- cloud membrane-contract
# Self-test the fixture HTTP routes
cargo run -p gestalt-cli -- cloud selftest-routes

See cli/reference.md for the full command set.

The React workbench is a browser surface for inspecting Gestalt crossings. It’s actively under development.

Terminal window
cd apps/react-workbench
npm install
npm run dev
# open http://127.0.0.1:5177

The MCP server exposes a curated set of inspection / simulation / proposal tools to LLM-driven agents and assistants. Production admission is blocked by manifest.

Terminal window
# See the manifest
cat mcp/gestalt-mcp.manifest.json
# Run the server (Node)
node mcp/server.mjs

See 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 you

The 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.

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.

Another process may already be on the port. Try --addr 127.0.0.1:3012.

The fixture bearer token is exactly fixture-session-token. Other fixture tokens used in tests:

fixture-session-token-secondary valid alternate
expired-fixture-session-token refuses with session_expired
wrong-scope-fixture-session-token refuses with scope_mismatch
tampered-fixture-session-token refuses with token_tampered

My 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.