Platform SDK
Define a model. Validate it before it runs.
Für alle, die auf der Plattform bauen: Hier steht, wie das System aufgebaut ist, welche Teile des SDK Sie nutzen können und wie Apps für Assistenten entstehen. Die Seite ist auf Englisch, weil Code, Schnittstellen und Fehlermeldungen es auch sind.
Applications import @platform/sdk
or a specific subpath. Start with a small domain definition, check incoming JSON,
and keep network calls and storage outside the model.
Available in the Platform workspace. External package distribution is not open yet. To use the services without the SDK, start with the apps or connect ChatGPT.
Reach every live surface with one ID
Each surface below is an MCP server behind the platform's own sign-in.
A hosted assistant adds the address as a custom connector and signs in as
you; a person building or operating on the platform signs in once from a
terminal with platform login --env dev
for the compass-platform.dev surfaces and platform login --env prod
for those on compass-platform.com, then lets platform agent
compose the same servers for Claude Code or Codex. The table is rendered from
the service catalogue, so it cannot say more than the catalogue does.
| Surface | Connector address | ChatGPT client | Claude client |
|---|---|---|---|
| Memory | https://memory.compass-platform.com/mcp | chatgpt-memory unmeasured | claude-ai-memory unmeasured |
| Tickets | https://tickets.compass-platform.com/mcp | chatgpt-tickets unmeasured | claude-ai-tickets unmeasured |
| Decisions | https://decisions.compass-platform.com/mcp | chatgpt-decisions unmeasured | claude-ai-decisions unmeasured |
| System Map | https://map.compass-platform.com/mcp | chatgpt-map unmeasured | claude-ai-map unmeasured |
| Factory | https://factory.compass-platform.com/mcp | chatgpt-factory unmeasured | claude-ai-factory unmeasured |
First workflow Connect Decisions and Tickets, ask the assistant to inspect one open decision, rule it yourself in the Factory, then have the assistant open the ticket that ruling implies. The getting started journey walks the same path with prompts you can paste.
Find the import for your app
The package root exports SDK_VERSION
and SURFACE. Check where an import
can run: in any JavaScript environment, in the browser, on the server, or during a build.
import { SDK_VERSION, SURFACE } from "@platform/sdk"
// Show the first three available imports.
console.log("Compass Platform SDK", SDK_VERSION)
for (const entry of SURFACE.slice(0, 3)) {
console.log(entry.specifier, entry.runtime)
}Expected resultNot run on this page
Compass Platform SDK 0.1.0 @platform/sdk/domain universal @platform/sdk/domain/core universal @platform/sdk/domain/schema universal
Output from the reference SDK baseline. Your checkout may list different imports or a different package version.
Define an order and its rules
Describe the states an order can have and the changes it accepts. Use
@platform/sdk/domain/core to check
the definition before connecting it to storage or other services.
- DefineCheck the model
- CompilePrepare an inert plan
- BindResolve approved functions
Save this as order.domain.ts in a Platform workspace package
that depends on @platform/sdk, then run
bun order.domain.ts. It prints Order without
creating records, making network calls, or granting permission.
import type { DomainDefinitionJSON } from "@platform/sdk/domain/schema"
import { bindDomain, compileDomain, defineDomain } from "@platform/sdk/domain/core"
export const orderDomain = {
schemaVersion: "domain-def@5",
name: "Order",
version: "1.0.0",
description: "An order that can be placed once.",
types: {
OrderState: { status: "'open' | 'placed'" },
Open: { status: "'open'" },
Empty: { "+": "reject" },
},
aggregates: {
Order: {
description: "One order, identified by orderId.",
urnPattern: "urn:shop:order:{orderId}", identity: "{orderId}",
state: { type: "OrderState", initial: { status: "open" } },
commands: {
place: {
description: "Place an open order.", inputType: "Empty",
cases: [{ when: "Open", emit: [{ type: "Placed", payload: {} }] }],
},
},
events: {
Placed: { description: "The order was placed.", type: "Empty",
apply: [{ op: "set", path: "status", value: "placed" }] },
},
},
},
resources: {},
authz: {
scopes: {},
policies: {},
defaults: { unauthenticatedAccess: "deny", missingRolesBehavior: "deny" },
},
} as const satisfies DomainDefinitionJSON
const definition = defineDomain(orderDomain)
const compiled = compileDomain(definition) // no I/O and no authority
const bound = bindDomain(compiled, { maps: {}, resources: {}, events: {} })
console.log(bound.compiled.source.definition.name) // OrderExpected resultNot run on this page
Order
The definition passed its checks. No data was saved and no service was called.
Next: see what happens when you place the order.
What do define, compile and bind do?
defineDomain preserves literal inference,
compileDomain validates and prepares deterministic behavior, and
bindDomain resolves symbolic references against a reviewed catalog.
Authority boundary A compiled domain is inert until you bind approved effects. Binding is not permission to act. The core preserves scope and confirmation metadata; it does not enforce caller authorization or human approval. Application adapters must enforce those gates and own network calls, identity, clocks, persistence, and external tools.
See a command change state
An aggregate keeps state and the rules that change it together.
Here, an open order accepts place, emits Placed,
and becomes placed. The pure kernel simulates that transition without writing
to a store. A second place is refused by the state rule.
- OpenAccept place
- Placed eventApply the declared change
- PlacedRefuse another place
Save as simulate.ts beside order.domain.ts and run
bun simulate.ts. After the imported example's Order line,
it prints open → placed.
import { apply, compileDomain, decide, defineDomain, initialState } from "@platform/sdk/domain/core"
import { orderDomain } from "./order.domain"
const plan = compileDomain(defineDomain(orderDomain))
const initial = initialState(plan, "Order")
if (!initial.ok) throw new Error(initial.error.code)
const decision = decide(plan, "Order", initial.value, {
name: "place", input: {}, identity: "example-1",
})
if (!decision.ok) throw new Error(decision.error.code)
let state = initial.value
for (const event of decision.value.events) {
const next = apply(plan, "Order", state, event, {
occurred_at: "2026-09-08T12:00:00.000Z", // explicit simulation time
})
if (!next.ok) throw new Error(next.error.code)
state = next.value
}
console.log(initial.value.status, "→", state.status) // open → placedExpected resultNot run on this page
Order open → placed
Importing the definition prints the first line. The second line shows the state change in memory; it is not a saved order.
Next: check a definition received as JSON.
From simulation to a running workflow
loadAggregate and executeCommand adapt aggregates
to event streams. Generated aggregate MCP tools expose commands allowed by
state; the host still enforces caller permission. Process runners support
human suspension/resumption, explicit retry policies and call budgets.
The host supplies inference and tool bindings, identity, clocks and storage.
Check a definition received as JSON
Use parseDomainDefinition(input: unknown) for data supplied by
a caller. If the definition is valid, you receive a normalized copy. Otherwise,
each error identifies the field to fix.
Save beside the previous example as parse-domain.ts. Run
bun parse-domain.ts: the imported example prints Order, then this
script prints the canonical JSON. The example uses the order definition as
its input; replace that stand-in with your caller's input.
import { compileDomain, parseDomainDefinition } from "@platform/sdk/domain/core"
import { orderDomain } from "./order.domain"
// Stand-in for JSON supplied by your caller. Replace with their input.
const receivedText = JSON.stringify(orderDomain)
const input: unknown = JSON.parse(receivedText)
const parsed = parseDomainDefinition(input)
if (!parsed.ok) {
for (const diagnostic of parsed.error.diagnostics) {
console.error(diagnostic.code, diagnostic.path, diagnostic.message)
}
throw new Error("Domain definition is invalid")
}
// Review any diagnostics before connecting the model to services.
for (const diagnostic of parsed.value.diagnostics) {
console.warn(diagnostic.code, diagnostic.path, diagnostic.message)
}
const compiled = compileDomain(parsed.value)
// The compiled result remains inert until an approved catalog is bound.
console.log(compiled.source.canonicalJson) // canonical definition JSONExpected resultNot run on this page
Prints Order, then the validated definition as canonical JSON.
Result summary, not a literal console transcript. The complete JSON includes the order state, command and event rules.
Understand validation results
A malformed JSON string fails at JSON.parse; a parsed but invalid
definition returns diagnostics. No invalid definition reaches compilation.
Successful parsing can also return diagnostics; review them and
resolve affected adapter paths before activating the model. Literal and JSON
inputs share the same structural, reference, semantic, and policy checks.
Connect an MCP tool to its view
An interactive MCP result is two related contracts: the tool advertises a predeclared UI resource, and the host reads that resource when it renders the result. Structured data must remain useful even when the visual is unavailable.
Metadata shape only. This fragment does not register a tool or resource and cannot render a widget by itself. Your MCP adapter must serve the declared resource, and the client must support MCP Apps.
interface InteractiveToolMetadata {
_meta: {
ui: { resourceUri: `ui://decisions/briefing.html` }
}
}
const metadata: InteractiveToolMetadata = {
_meta: { ui: { resourceUri: "ui://decisions/briefing.html" } },
}Expected resultNot run on this page
Metadata only · no widget rendered
Defines the resource address ui://decisions/briefing.html. This fragment prints nothing. Register the tool, serve that resource, and test it in a host that supports MCP Apps.
Tool result
Validated data for the model and any client that does not mount the visual.
UI resource
The address of the view the host can load. The view must be served separately.
Use the root workspace today
Platform applications currently consume the SDK through the repository's single Bun workspace. Do not create a nested workspace or import an implementation package as a competing application-facing authority.
Forge, the platform's Git service, holds your repositories under your platform sign-in: git.compass-platform.dev.
External package distribution is not open yet. A reproducible Docker toolchain and the eventual package delivery contract are not an installation path offered by this reference.
Updating an existing integration? See the release notes for the import contract, compatibility guidance and checks.