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-appExpected output:
Build complete
Output .fabricharness/build/databricks-app
Manifest .fabricharness/build/databricks-app/manifest.json
Jobs artifact-generator, campaign-reviewerUse --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.mdThe 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:
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 };
},
});{
"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/nullFor 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.mjsThe 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.sha256A 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.