FabricFabricHarness
Deployment

Portable Agent Packages

Build self-contained agent artifacts that can be handed from an application repository to Databricks, Runway, or another deployment system.

A portable agent package is the complete directory emitted by fh build. It is a deployment artifact—not an npm package—and is the supported boundary between the repository that authors an agent and the system that deploys it. Consumers stage the directory without cloning the producer repository, resolving its pnpm workspace, or publishing private shared-contract packages.

This is useful when one repository owns agent definitions and another owns infrastructure. For example, Fabric GTM Brain can build its jobs once and Fabric Runway can stage that output into a Unity Catalog Volume before deploying a Databricks App.

Build the package

Run the command from the package that owns .fabricharness/:

pnpm exec fh build --target databricks-app

Expected output:

Build complete
  Output    .fabricharness/build/databricks-app
  Manifest  .fabricharness/build/databricks-app/manifest.json
  Jobs      artifact-generator, campaign-reviewer

Use --target node for a generic server artifact. Docker, Kubernetes, Azure, Render, Foundry, and Databricks App targets inherit the same definition-bundling behavior from @fabric-harness/node.

What is self-contained

Harness bundles each discovered job or persistent agent into an .mjs module and bundles the shared v2 HTTP server. A Databricks App artifact includes:

.fabricharness/build/databricks-app/
  .fabricharness/
    config.mjs
    jobs/*.mjs
    agents/*.mjs
    roles/
    skills/
  dist/
    jobs/*.mjs
    agents/*.mjs
    server.mjs
  app.yaml
  databricks.yml
  Dockerfile
  manifest.json
  package.json
  DATABRICKS_APP_DEPLOY.md

The package contains:

  • agent and job implementation code;
  • workspace-owned TypeScript contracts and barrel exports imported by those definitions;
  • the Harness server and definition loader needed by the selected target;
  • roles, skills, generated configuration, route metadata, and trigger declarations; and
  • target descriptors such as app.yaml, databricks.yml, or a Dockerfile.

The manifest records every definition name, kind, trigger, relative entrypoint, and SHA-256 digest. External staging systems should treat the entire output directory as one immutable unit.

Databricks App definition bundles are minified without source maps so workspace snapshot export can consume them directly. Harness verifies the 10 MiB Databricks per-file limit for every emitted job, persistent agent, and server bundle during fh build; an oversized definition fails the build before the artifact can be handed to Runway or deployed.

Monorepo contracts

Agent packages commonly import schemas from a sibling workspace package:

.fabricharness/jobs/campaign-reviewer.ts
import { campaignReviewInput } from '@acme/gtm-agent-contracts';
import { defineAgent } from '@fabric-harness/sdk';

export default defineAgent({
  name: 'campaign-reviewer',
  input: campaignReviewInput,
  triggers: { webhook: true },
  async run({ input }) {
    return { campaignId: input.campaignId, approved: true };
  },
});
agents/package.json
{
  "dependencies": {
    "@acme/gtm-agent-contracts": "workspace:*",
    "zod": "catalog:"
  }
}

Harness follows dependencies resolved through workspace:, catalog:, file:, link:, portal:, and patch:. Their executable code is bundled and those source-only specifiers are omitted from the generated runtime package.json. TypeScript barrels and extensionless workspace imports are resolved during the build, before metadata is read from the emitted definition.

Registry dependencies that the target deliberately keeps external remain in package.json. Install only those declared dependencies in the detached directory. Do not copy the producer's node_modules tree.

Verify detachment

At minimum, make the handoff fail if a source-only protocol or private workspace import remains:

artifact=.fabricharness/build/databricks-app

test -s "$artifact/manifest.json"
test -s "$artifact/dist/server.mjs"
test -s "$artifact/app.yaml"
test -s "$artifact/databricks.yml"

if grep -R -E '"(workspace|catalog|file|link|portal|patch):' "$artifact/package.json"; then
  echo 'source-only dependency escaped into the runtime package' >&2
  exit 1
fi

jq -e '.schemaVersion == 2 and (.jobs | type == "array") and (.agents | type == "array")' \
  "$artifact/manifest.json" >/dev/null

For a stronger isolation test, copy the artifact outside both repositories and start it without the producer workspace on Node's resolution path:

detached="$(mktemp -d)"
cp -R .fabricharness/build/databricks-app/. "$detached/"

cd "$detached"
npm install --omit=dev
PORT=8080 node dist/server.mjs

The server may then fail because a required runtime binding is absent—for example Lakebase, FABRIC_HARNESS_API_TOKEN, or a model provider credential. That is a deployment-configuration failure, not a packaging failure. A module-resolution error for a workspace package is a packaging failure.

Hand off to Runway or another deployer

The consumer receives only the built directory plus an immutable content digest:

tar -C .fabricharness/build -czf gtm-agents-databricks-app.tgz databricks-app
sha256sum gtm-agents-databricks-app.tgz > gtm-agents-databricks-app.tgz.sha256

A deployment system can unpack it, validate manifest.json, calculate its own directory digest, and stage the files in an artifact store or Unity Catalog Volume. It must not rebuild agent source, rewrite bundled definitions, or resolve the producer's workspace packages. Environment-specific Databricks resource names and secrets are applied after staging.

The artifact is safe to cache by digest. Rebuilding can change generated timestamps or archive bytes, so evidence should bind the exact directory or archive supplied to the deployer rather than assuming two independent builds are byte-identical.

Databricks runtime bindings

Self-contained describes code and package resolution; it does not embed credentials or managed resources. Bind these at deployment time as needed:

  • App OAuth M2M identity and allowed principals;
  • OBO user authorization for per-user governance;
  • Lakebase host, database, endpoint, and credential exchange;
  • Genie Agent, SQL warehouse, Unity Catalog, and MLflow resources; and
  • model-provider or AI Gateway configuration.

fh doctor and the generated Databricks bundle fail closed when the App claims Lakebase or Genie without the corresponding resource binding. Secrets remain references and are never copied into the bundle or model context.

Failure behavior

The build fails before producing a successful manifest when:

  • an agent definition cannot be bundled or imported from its emitted module;
  • a workspace dependency cannot be resolved;
  • two definitions produce an invalid or conflicting public name; or
  • the target cannot generate its required runtime files.

Deployment verification should fail when:

  • manifest.json, the server entrypoint, or target descriptors are missing;
  • a source-only dependency protocol remains in the generated package;
  • manifest hashes do not match the shipped definitions;
  • a production route lacks triggers.webhook: true; or
  • the detached process still imports a private producer package.

For the Databricks production path, also require /api/health, /api/ready, finite-job lifecycle, restart recovery, and any claimed Lakebase/Genie/OBO behavior before promotion.

See Build and run artifacts, Databricks App deployment, and Build manifest.