FabricFabricHarness
Deployment

Build and Run Artifacts

Package v2 jobs and persistent agents into self-contained deployable artifacts.

fh build compiles the embedded workspace, bundles authored definitions, and emits a target-specific runtime. Node-derived targets use the same v2 HTTP server as fh dev, so local and deployed routes have the same semantics.

Start with both definition types

Finite work lives in .fabricharness/jobs/:

.fabricharness/jobs/summarize.ts
import { job, schema } from '@fabric-harness/sdk';

export default job({
  name: 'summarize',
  input: schema.object({ text: schema.string() }),
  output: schema.string(),
  triggers: { webhook: true },
  async run({ init, input }) {
    const session = await (await init()).session();
    return session.prompt(`Summarize:\n\n${input.text}`);
  },
});

Long-lived addressable agents live in .fabricharness/agents/:

.fabricharness/agents/support.ts
import { defineAgent } from '@fabric-harness/sdk';

export default defineAgent(({ id }) => ({
  name: 'support',
  instructions: `You are the support agent for account ${id}.`,
  model: 'openai/gpt-5.5',
  triggers: { webhook: true },
}));

Build Node

fh build --target node

The build summary distinguishes the two surfaces:

Build complete
  Output    .fabricharness/build/node
  Manifest  .fabricharness/build/node/manifest.json
  Jobs      summarize
  Agents    support

The artifact contains the shared server and self-contained definition modules:

.fabricharness/build/node/
  .fabricharness/
    agents/support.mjs
    jobs/summarize.mjs
    roles/
    skills/
  dist/
    agents/support.mjs
    jobs/summarize.mjs
    server.mjs
  manifest.json
  package.json

Run it:

PORT=8080 OPENAI_API_KEY="$OPENAI_API_KEY" \
  node .fabricharness/build/node/dist/server.mjs

Invoke the built artifact

Finite jobs return their result and a generated run ID:

curl -sS http://localhost:8080/jobs/summarize \
  -H 'content-type: application/json' \
  -d '{"text":"Fabric builds durable agents."}'
{
  "result": "Fabric is a framework for durable agents.",
  "runId": "8f2d...",
  "agentPath": "/app/.fabricharness/jobs/summarize.mjs"
}

Persistent input is admitted durably and returns 202:

curl -i http://localhost:8080/agents/support/acct-42 \
  -H 'content-type: application/json' \
  -d '{"message":"Where is my invoice?"}'
{
  "submissionId": "5ec1...",
  "streamUrl": "/agents/support/acct-42/conversation?session=default&offset=0",
  "offset": "0"
}

Use @fabric-harness/client to wait for settlement:

import { createFabricClient } from '@fabric-harness/client';

const client = createFabricClient({ baseUrl: 'http://localhost:8080' });
const admitted = await client.agents.send({
  agent: 'support',
  id: 'acct-42',
  message: 'Where is my invoice?',
});
const settled = await client.agents.wait({
  agent: 'support',
  id: 'acct-42',
  submissionId: admitted.submissionId,
});

Manifest schema v2

Every current artifact emits schemaVersion: 2 with separate collections:

jq '{schemaVersion, jobs, agents}' .fabricharness/build/node/manifest.json

Build readers in @fabric-harness/node normalize legacy schema-v1 manifests, but external tooling should migrate to jobs and agents. See Build manifest.

Production exposure

In production, a public job or persistent agent must declare triggers.webhook: true. Protect the server and scope tenants with environment variables:

NODE_ENV=production \
FABRIC_HARNESS_API_TOKEN="$API_TOKEN" \
FABRIC_HARNESS_TENANT_REQUIRED=1 \
FABRIC_HARNESS_RATE_LIMIT_MAX=100 \
FABRIC_HARNESS_RATE_LIMIT_WINDOW_MS=60000 \
node dist/server.mjs

Clients then send both headers:

curl http://localhost:8080/jobs/summarize \
  -H "authorization: Bearer $API_TOKEN" \
  -H 'x-fabric-tenant: acme' \
  -H 'content-type: application/json' \
  -d '{"text":"..."}'

Other targets

fh build --target docker --docker-build --docker-tag acme/support:1.0.0
fh build --target databricks-app
fh build --target aks
fh build --target aca

Node-derived targets inherit the shared v2 server. Cloudflare supports finite jobs and serialized Durable Object-backed persistent sessions. The Cloudflare runtime intentionally omits the Node submission queue, abort, attachment materialization, and offset conversation-stream routes.

Verify supply-chain metadata

fh build --target docker --provenance --attestation --sbom
fh verify-provenance .fabricharness/build/docker
fh verify-attestation .fabricharness/build/docker

See Docker, Databricks App, and Security hardening.