FabricFabricPlatform
Platform referenceReference

Platform Host 0.7

Install and operate the durable governed-action host for applications called by Hermes, Harness, MCP, and other agent runtimes.

@fabricorg/platform-host is the application-side runtime for governed mutations. It creates the durable invocation before dispatch, validates the registered action schema, enforces authorization and policy, coordinates adapters, appends canonical events, and supports approval and interruption recovery.

An external agent such as Hermes does not install this package merely to call a Fabric application. The application installs Platform Host and exposes a narrow authenticated REST or MCP gateway. The agent receives only that gateway, a registration-bound credential, and the actions allowed by its current grant.

IntegratorInstall @fabricorg/platform-host?Correct boundary
Application or vertical ownerYesRegister actions and run the governed Host
Hermes, Fabric Harness, or another external agentNoDiscover and invoke the application's REST/MCP gateway
Durable worker owned by the applicationYesCall executeInvocation() for a persisted invocation

Install

Platform Host 0.7 requires Node.js 20 or newer and Platform 0.9:

pnpm add @fabricorg/platform@^0.9.0 @fabricorg/platform-host@^0.7.0
npm install @fabricorg/platform@^0.9.0 @fabricorg/platform-host@^0.7.0

The package supports ESM and CommonJS. Version 0.7.0 publishes Host contract generation 2. Applications should record both values in runtime evidence because the npm version and durable contract generation evolve independently.

Wire the host

The ingress layer authenticates the caller and creates any immutable admission record. It then submits a trusted command to the Host. Do not pass credentials, bearer tokens, signatures, nonces, or private provider configuration as action parameters.

import {
  createGovernedActionHost,
  PostgresPlatformHostStore,
} from "@fabricorg/platform-host";

const store = new PostgresPlatformHostStore(
  domainStore,
  pool,
  {
    run: async (run) => {
      const client = await pool.connect();
      try {
        await client.query("BEGIN");
        const result = await run({
          db: domainStore.withClient(client),
          sql: client,
        });
        await client.query("COMMIT");
        return result;
      } catch (error) {
        await client.query("ROLLBACK");
        throw error;
      } finally {
        client.release();
      }
    },
  },
);

await store.ensureSchema();

const host = createGovernedActionHost({
  store,
  resolveAction: (actionId) => actionCatalog.get(actionId),
  authorization: {
    checkEntitlement,
    authorize: authorizeSubmission,
    authorizeExecution: async ({
      invocation,
      parameters,
      executionReason,
    }) =>
      admissionStore.revalidate({
        admissionId: invocation.authorizationBindingId,
        tenantId: invocation.tenantId,
        actorId: invocation.actorId,
        actionId: invocation.actionId,
        parameters,
        executionReason,
      }),
  },
  runtimeEvidence: {
    hostPackageVersion: "0.7.0",
    policyRulesetVersion: "application-policy.v1",
  },
});

domainStore.withClient(client) is application-owned: it must return the same domain-store interface bound to the supplied PostgreSQL/Lakebase transaction. The transaction provider is what allows domain writes, event sequence allocation, canonical event append, and invocation finalization to commit or roll back as one unit.

Production applications use PostgresPlatformHostStore with Lakebase or PostgreSQL and run ensureSchema() during controlled startup or migration. MemoryPlatformHostStore is for tests and explicit local development.

Submit an admitted agent command

Create the admission before starting background orchestration. Persist only an opaque, non-secret binding on the invocation:

const admission = await admissionStore.admit({
  tenantId,
  registrationId,
  actorId,
  actionId,
  parameters,
  idempotencyKey,
});

const submitted = await host.submitAction({
  actionId,
  parameters,
  tenantId,
  spaceId,
  actorId,
  actorType: "agent",
  idempotencyKey,
  authorizationBindingId: admission.id,
  correlationId,
});

The same logical request must reuse the same idempotency key and canonical parameters. A caller retry must not mint a new random key. A changed request under an existing key should be rejected by the application admission boundary.

Submission authorization answers whether the caller may create the durable invocation. authorizeExecution revalidates the original actor, schema-parsed durable parameters, current registration and resource scope immediately before policies and mutation code. It runs with an executionReason of:

  • initial for first execution;
  • approval_resume after an approval decision;
  • recovery for interrupted work.

Revocation can therefore stop work that was admitted earlier but has not executed. An already completed idempotent replay remains observable and does not execute again.

Events, adapters, and recovery

Use eventPhase: "before_adapters" for durable intent/outbox events. With the PostgreSQL transaction provider, the handler's domain writes and those declared events commit atomically.

Use eventPhase: "after_adapters" only for completion or attestation events whose truth requires every configured adapter to have succeeded. In Host 0.7, the completion event and invocation completion commit together. If finalization fails after an adapter succeeds, recovery reuses the succeeded adapter checkpoint and retries finalization without repeating the external effect.

Adapters still need a stable provider idempotency identity and an explicit reconciliation path for ambiguous remote outcomes. A non-idempotent action with unknown side effects must fail closed for operator reconciliation; it must not blindly rerun after a stale lease.

Agent-facing gateway contract

The application gateway—not the model—must:

  1. authenticate a tenant- and registration-bound credential;
  2. derive actorId and actorType server-side;
  3. filter discovery to explicitly reviewed actions in the current grant;
  4. require a stable idempotency key for every mutation;
  5. stage private or large content and submit only an opaque reference where necessary;
  6. pass the opaque admission ID as authorizationBindingId;
  7. surface actionInvocationId, status, approval state, and privacy-safe errors;
  8. provide status and reconciliation operations scoped to the same registration.

The agent must never call action handlers, adapters, Temporal workflows, or the database directly. Temporal may dispatch and recover a Host invocation, but it is not a second mutation authority.

Production checklist

  • Register each mutation as a FabricModule action with a stable schema and declared events.
  • Configure both submission and execution-time authorization for delegated agents.
  • Use a transaction provider for atomic domain writes and canonical events.
  • Configure a durable dispatcher/worker; inline execution is only for tests and local development.
  • Keep secrets out of parameters and use redactActionParameters as defense in depth.
  • Use stable command and provider idempotency identities.
  • Revalidate resource scope before prospect-visible or otherwise external adapter effects.
  • Resume approvals through resumeApprovedInvocation() instead of calling mutation code directly.
  • Rebuild projections from listEvents() with sequence-monotonic checkpoints.
  • Test policy denial, tenant isolation, revocation, adapter failure, crash recovery, and idempotent replay.

See Agent HITL for approval routing and Mutation pipeline for stage ordering.

On this page