# Fabric Harness complete documentation Canonical index: https://harness.fabric.pro/llms.txt License: Apache-2.0 Fabric Harness is a TypeScript framework for governed, durable autonomous agents. Databricks architecture and live certification, enterprise identity/RBAC, channels, databases, sandboxes, MCP, operator controls, and deployment guidance are prioritized in this corpus. --- # What is Fabric Harness Canonical: https://harness.fabric.pro/docs A TypeScript framework for building durable, deployable autonomous agents. Fabric Harness is a **headless** TypeScript framework for building **durable, deployable autonomous agents**. Finite jobs live in `.fabricharness/jobs/`; persistent, addressable agents live in `.fabricharness/agents/`. Run them locally and build for Node, Docker, Temporal, Cloudflare, Azure-oriented targets, or Databricks Apps and Model Serving proxy deployments. > **Headless runtime, optional clients.** Agents run through the SDK, HTTP, schedules, channels, or > Temporal without requiring a UI. Use `fh console` for terminal interaction or > `@fabric-harness/react` for an application UI; both consume the same authenticated public protocol. ## Why a framework, not just an SDK Most agent libraries leave you to wire up the runtime, the build, the dev server, the deployment story, and the durability story yourself. Fabric Harness is opinionated about those things so the agent code stays focused on the work: - **Workspace conventions** — `.fabricharness/jobs`, `agents`, `roles`, `skills`, `policies`, `sandboxes`, plus a project `AGENTS.md`. - **CLI** — discover, run, build, deploy, inspect, replay, and verify everything from one binary (`fabric-harness` or `fh`). - **Runtime adapters** — local Node, Docker sandbox, Temporal worker, Cloudflare Workers, Foundry-hosted, more on the way. - **Headless by default** — agents complete autonomously. Approvals are an explicit hook, not a default user prompt. - **Durable by design** — long-running sessions can survive crashes, restarts, retries, and human approval delays via Temporal. - **Capability-scoped security** — commands and tools are explicit, secrets stay out of model context. ## How Fabric Harness works Definitions declare what an agent can do. The runtime creates an isolated session, assembles roles, skills, tools, policy, and context, then drives the model loop. Every tool or shell action passes through capability policy before a sandbox or provider adapter executes it. Events, approvals, artifacts, cost, and results remain correlated to the session and submission. ```mermaid flowchart LR INPUT[CLI, HTTP, schedule, or channel] --> DEF[Job or persistent agent] DEF --> SESSION[Session runtime] subgraph Context[Context assembly] ROLE[Roles] SKILL[Skills] MEMORY[Memory and mounted sources] end ROLE --> SESSION SKILL --> SESSION MEMORY --> SESSION SESSION --> MODEL[Model provider] MODEL --> ACTION{Next action} ACTION -->|Tool| POLICY[Capability policy] ACTION -->|Shell or file| POLICY ACTION -->|Final result| RESULT[Typed result and events] POLICY -->|Approval needed| APPROVAL[Durable approval] POLICY -->|Allowed| EXEC[Sandbox or connector] APPROVAL --> EXEC EXEC --> SESSION SESSION --> STORE[(Session and submission store)] classDef entry fill:#f4f4f5,stroke:#71717a,color:#18181b classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef control fill:#fef3c7,stroke:#d97706,color:#422006 classDef state fill:#dcfce7,stroke:#16a34a,color:#052e16 class INPUT,DEF entry class SESSION,MODEL,RESULT fabric class ACTION,POLICY,APPROVAL control class EXEC,STORE,ROLE,SKILL,MEMORY state ``` On Databricks, the same runtime can use Model Serving, SQL Warehouses, Unity Catalog, Vector Search, Genie, Feature Serving, Lakeflow, Lakebase, MLflow, and Databricks Apps under one propagated identity. Start with [Fabric Harness on Databricks](/docs/databricks). ## Standard agent terminology Fabric Harness keeps the terms that are converging across the agent ecosystem. You will find these everywhere in the docs: | Term | Meaning | | --- | --- | | **Agent** | A configured autonomous runtime. | | **Session** | A persisted message/context thread. | | **Skill** | A reusable Markdown- or code-backed procedure. | | **Role** | A scoped instruction/model profile. | | **Sandbox** | An isolated execution environment with filesystem/shell/tools. | | **Task** | A child or delegated agent run. | | **Tools** | Model-callable functions. | | **Commands** | Shell-level capabilities exposed to the sandbox. | | **Build** | A compiled, deployable workspace artifact. | ## One SDK, one import, two flavours Most users only ever need one import: ```ts import { agent, schema } from '@fabric-harness/sdk'; ``` The bare `@fabric-harness/sdk` import gives finite jobs `agent({...})` / `job({...})` with **headless defaults pre-injected** on every `init()` call (`runtime: 'stateless'`, `sandbox: 'virtual'`, `loopRuntime: pi-agent-core`, `compaction: { enabled: true }`). Definition-level instructions, tools, policies, cost budgets, and approval timeouts are applied as runtime defaults; call-level roles override instructions, while policies and budgets retain the definition's security floor. For **Temporal-backed durable agents** or **compliance/audit workloads** where you don't want any implicit defaults, switch to `@fabric-harness/sdk/strict` — same call shape, defaults are *not* injected. Every option must be declared explicitly. **Runtime** (`stateless` / `inline` / `temporal`) and **target** (`node`, `temporal-worker`, `docker`, `cloudflare`, Azure-oriented targets, `databricks-app`, `databricks-serving`) are separate choices configured at `init()` and `fh build`. ```ts title="Default — bare import, headless defaults" import { agent } from '@fabric-harness/sdk'; export default agent<{ message: string }>({ name: 'echo', model: 'openai/gpt-5.5', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); return { reply: await session.prompt(input.message) }; }, }); ``` ```ts title="Strict — every option declared, Temporal-safe" import { agent, schema } from '@fabric-harness/sdk/strict'; export default agent({ name: 'triage', model: 'openai/gpt-5.5', input: schema.object({ issueNumber: schema.number(), title: schema.string() }), output: schema.object({ severity: schema.enum(['low','medium','high']), summary: schema.string() }), triggers: { webhook: true, schedule: '*/15 * * * *' }, run: async ({ init, input }) => { const fabric = await init({ runtime: 'temporal', sandbox: 'local', compaction: { enabled: false }, policy: triagePolicy, }); const session = await fabric.session(); return await session.prompt(`Triage issue #${input.issueNumber}: ${input.title}`); }, }); ``` Both examples deploy through the same CLI and use the same `init()` / `session.prompt()` / `session.skill()` / `session.task()` / `session.shell()` surface. Start with the default import; switch to `/strict` when you need replay-determinism (Temporal) or compliance/audit clarity. You do not rewrite the agent. See [SDK entrypoints, runtimes, and targets](/docs/reference/sdk-entrypoints-runtimes-targets) for the full distinction. ## Public API surface ```ts const fabricAgent = await init(options); const session = await fabricAgent.session(id?, options?); await session.prompt(text, options?); await session.skill(name, options?); await session.task(text, options?); await session.shell(command, options?); ``` ## Where to go next - **Want the 10-line agent?** [Headless agents with the minimal entrypoint](/docs/getting-started/headless-mode). - **Confused by entrypoints, stateless, inline, or Temporal?** [SDK entrypoints, runtimes, and targets](/docs/reference/sdk-entrypoints-runtimes-targets). - **New here?** [Use cases](/docs/use-cases) → [Installation](/docs/getting-started/installation) → [Your first agent](/docs/getting-started/first-agent). - **Want CLI specifics?** [CLI reference](/docs/cli). - **Ready to deploy?** [Deployment](/docs/deployment). - **Building on Databricks?** [Architecture, integrations, quickstart, and validation](/docs/databricks). - **Curious about scope?** See the [capability matrix](/docs/reference/capability-matrix). --- # Fabric Harness on Databricks Canonical: https://harness.fabric.pro/docs/databricks Build governed TypeScript agents with Unity AI Gateway, Unity Catalog, SQL Warehouses, Lakebase, Vector Search, MLflow, Jobs, and Databricks Apps. Fabric Harness provides a first-party Databricks package, Databricks deployment targets, and Unity Catalog-aware connectors. It is designed for agents that need governed access to enterprise data, durable state, approval controls, lineage, and cost attribution without replacing Databricks authorization. ```mermaid flowchart LR U[User or system] --> I[HTTP, schedule, channel, or CLI] I --> H[Fabric Harness agent] subgraph Control[Fabric control plane] H --> P[Policy and approvals] H --> S[Durable session runtime] H --> T[Governed tools] end subgraph Databricks[Databricks data and AI services] T --> M[Unity AI Gateway] T --> Q[SQL Warehouse] T --> V[Vector Search] T --> G[AI/BI Genie] T --> F[Feature Serving] T --> L[Lakeflow Jobs] S --> B[Lakebase] T --> C[Unity Catalog] end C --> D[(Tables and volumes)] H --> O[MLflow and OpenTelemetry] classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef data fill:#dcfce7,stroke:#16a34a,color:#052e16 classDef control fill:#fef3c7,stroke:#d97706,color:#422006 class H fabric class M,Q,V,G,F,L,B,C,D data class P,S,T control ``` ## What is first party | Area | Fabric Harness surface | Databricks service | | --- | --- | --- | | Model runtime | `databricksFoundationModelProvider()` | Unity AI Gateway model services and custom Model Serving endpoints | | Data access | `databricksSqlTool()`, `databricksSqlSandbox()` | SQL Statement Execution and SQL Warehouses | | Governance | `withGovernance()`, policy and approval helpers | Unity Catalog remains the authorization authority | | Retrieval | `databricksVectorSearch()`, `databricksEmbeddings()` | Mosaic AI Vector Search and Model Serving | | Analytics | `databricksGenieTool()`, `databricksAiQueryTool()` | AI/BI Genie and AI Functions | | Features | `databricksFeatureLookupTool()` | Feature Serving | | Orchestration | Jobs, notebooks, and `databricksLakeflowTools()` | Jobs and Lakeflow pipelines | | Durable state | `lakebaseClient()`, `databricksPersistence()` | Lakebase Autoscaling | | Files | Volume source/writer and `UcVolumesAttachmentStore` | Unity Catalog Volumes and Files API | | Operations | MLflow tracing, usage capture, consumption, actual-cost budgets | MLflow and system tables | | Hosting | `databricks-app`, `databricks-serving` | Databricks Apps and Model Serving proxy | ## Choose the right starting point - Read [Databricks development with Fabric Harness](/docs/databricks/development) to understand the local-to-App workflow, supported workload patterns, AI Gateway integration, and where Fabric adds value. - Use [Fabric Desktop for Databricks projects](/docs/databricks/fabric-desktop) for a guided recipe, workspace discovery, mock/live run, build, preview, and Databricks App deployment workflow. - Use the [quickstart](/docs/databricks/quickstart) to run a mocked agent and then connect a workspace. - Read [architecture](/docs/databricks/architecture) to understand identity, control, and data flow. - Use [integrations](/docs/databricks/integrations) as the feature and API map. - Choose an execution surface with [compute patterns](/docs/databricks/compute). - Read [connectors and sandboxes](/docs/databricks/sandboxes-connectors) before choosing SQL execution, Volumes, or a general-purpose code sandbox. - Apply the [enterprise controls](/docs/databricks/enterprise) for production identity, approvals, audit, durability, and cost limits. - Use the evidence-driven [workspace compatibility matrix](/docs/databricks/compatibility) for cloud, region, auth, API, App, and Lakebase requirements. - Use [submission readiness](/docs/databricks/submission-readiness) to collect evidence for a Databricks partner technical review. ## Workspace validation Before deploying, run the Databricks certification command against the target workspace. It verifies OAuth scopes, Unity Catalog grants, API access, App resources, Unity AI Gateway model services, SQL Warehouses, and Lakebase connectivity while producing secret-redacted evidence for the deployment record. Follow the [submission readiness guide](/docs/databricks/submission-readiness) for the exact configuration and commands. --- # Databricks architecture Canonical: https://harness.fabric.pro/docs/databricks/architecture How Fabric Harness routes identity, policy, model calls, data tools, durable state, telemetry, and deployments through Databricks. Fabric Harness separates the agent runtime from the services the agent is allowed to call. A single Databricks principal can be threaded through Model Serving, REST tools, SQL, and Lakebase credential exchange. Fabric policy adds approvals, egress restrictions, budgets, and audit correlation; Unity Catalog still makes the final data authorization decision. ## Runtime layers ```mermaid flowchart TB subgraph Entry[Invocation] CLI[fh run] API[Jobs and agents API] EVT[Schedule or channel] end subgraph Runtime[Fabric Harness runtime] DEF[Job or persistent agent definition] SES[Session and model loop] POL[Capability policy] APR[Human approval] AUD[Audit, lineage, and cost] end subgraph Identity[Databricks identity] APP[App service principal] OBO[On-behalf-of user] M2M[OAuth M2M] end subgraph Services[Databricks services] SERVE[Model Serving] SQL[SQL Warehouse] UC[Unity Catalog] RAG[Vector Search] STATE[Lakebase] OPS[MLflow and system tables] end CLI --> DEF API --> DEF EVT --> DEF DEF --> SES SES --> POL POL -->|sensitive action| APR POL -->|allowed action| Services SES --> AUD APP --> SES OBO --> SES M2M --> SES SERVE --> UC SQL --> UC RAG --> UC STATE --> AUD OPS --> AUD classDef entry fill:#f4f4f5,stroke:#71717a,color:#18181b classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef identity fill:#fef3c7,stroke:#d97706,color:#422006 classDef dbx fill:#dcfce7,stroke:#16a34a,color:#052e16 class CLI,API,EVT entry class DEF,SES,POL,APR,AUD fabric class APP,OBO,M2M identity class SERVE,SQL,UC,RAG,STATE,OPS dbx ``` ## One request, one governed identity ```mermaid sequenceDiagram autonumber actor User participant App as Databricks App participant Fabric as Fabric Harness participant Policy as Policy and approvals participant Model as Model Serving participant Tool as SQL or Databricks API participant UC as Unity Catalog participant State as Lakebase User->>App: Prompt or job input App->>Fabric: Request plus app or user identity Fabric->>State: Load session and submission Fabric->>Model: Prompt with short-lived OAuth token Model-->>Fabric: Tool request Fabric->>Policy: Evaluate capability and approval rules alt approval required Policy-->>User: Approval request User-->>Policy: Approve or deny end Policy->>Tool: Execute as the governed principal Tool->>UC: Enforce catalog, schema, table, row, and column grants UC-->>Tool: Authorized result or denial Tool-->>Fabric: Redacted result plus lineage metadata Fabric->>State: Persist events and final result Fabric-->>User: Typed response or durable receipt ``` ## Finite jobs and persistent agents Finite jobs live in `.fabricharness/jobs/`, return one typed result, and are invoked at `POST /jobs/:name`. Persistent agents live in `.fabricharness/agents/`, retain an addressable conversation, and return a submission receipt from `POST /agents/:name/:id`. Both use the same model, tool, policy, and Databricks integration surfaces. Lakebase can back sessions, submissions, and conversation streams. The runtime exchanges a workspace OAuth token for a short-lived database credential, supplies that credential through the Postgres pool, refreshes early, and deduplicates concurrent refreshes. ## Deployment boundaries `databricks-app` runs the bundled Node server inside Databricks Apps. `databricks-serving` builds an MLflow `ChatAgent` proxy that calls an agent hosted elsewhere; it does not execute the TypeScript runtime inside Model Serving. The proxy uses asynchronous Harness admission and streams model deltas to Model Serving as `ChatAgentChunk` values, while preserving a normal aggregated `predict` response. See [Databricks deployment](/docs/deployment/databricks) for target details. --- # Databricks quickstart Canonical: https://harness.fabric.pro/docs/databricks/quickstart Scaffold, mock, connect, build, and deploy a governed Databricks agent with Fabric Harness. ## Create the workspace ```sh npx @fabric-harness/cli init --template databricks --dir analytics-agent cd analytics-agent npm install ``` The template creates a finite analytics job, role, skill, Databricks App config, governed SQL policy, certification manifest, `AGENTS.md`, and environment sample. Lakebase coordinates are optional. ```ts title=".fabricharness/jobs/databricks-analyst.ts" import { defineDatabricksAgent } from '@fabric-harness/databricks'; import { schema } from '@fabric-harness/sdk'; import policy from '../policies/databricks.js'; export default defineDatabricksAgent({ name: 'databricks-analyst', description: 'Answer governed questions using SQL and Unity Catalog.', input: schema.object({ question: schema.string() }), output: schema.string(), model: 'system.ai.gpt-oss-20b', tools: ['sql', 'tables', 'table-info'], triggers: { manual: true, webhook: true }, sandbox: 'empty', policy, }); ``` ## Validate without a workspace ```sh fh doctor --getting-started --tools fh agents fh describe databricks-analyst fh run databricks-analyst --question "Describe main.sales.orders" --mock ``` Mock mode covers discovery, schemas, tool assembly, model-loop behavior, and HTTP routing. It cannot validate Databricks permissions or API behavior. Read [Local naming and authentication](/docs/databricks/local-naming-auth) before connecting a workspace. It distinguishes job, persistent-agent, App, model, Fabric principal, tenant, and Databricks principal identities with PAT, OAuth M2M, and OBO examples. ## Add products with recipes On an existing Fabric project, scaffold individual Databricks surfaces: ```sh fh add databricks core fh add databricks sql fh add lakebase fh add vector-search fh add lakeflow fh add jobs ``` See [Databricks recipes](/docs/databricks/recipes) for the full catalog, aliases (`lakehouse`, `databricks-rag`, …), and how recipes map to examples. ## Connect a workspace ```dotenv title=".env.local" DATABRICKS_HOST=https:// DATABRICKS_CLIENT_ID=00000000-0000-0000-0000-000000000000 DATABRICKS_CLIENT_SECRET=resolve-from-a-secret-store DATABRICKS_WAREHOUSE_ID=0123456789abcdef DATABRICKS_MODEL=system.ai.gpt-oss-20b DATABRICKS_INFERENCE_MODE=auto # Optional explicit override: # DATABRICKS_AI_GATEWAY_BASE_URL=https:///ai-gateway/mlflow/v1 ``` Grant the service principal workspace access, `CAN QUERY` on the SQL Warehouse, permission to invoke the model service, and only the required Unity Catalog privileges. Then run the same command without `--mock`. Copy `DATABRICKS_HOST` from the workspace URL, including `https://` and without a trailing path. `system.ai.*` models route through Unity AI Gateway at `${DATABRICKS_HOST}/ai-gateway/mlflow/v1`. Custom endpoint names continue to route through `/serving-endpoints`; set `DATABRICKS_INFERENCE_MODE` when you need to choose explicitly. ## Use the composable bundle For more integrations, configure the `databricks()` bundle and pass its surfaces to `init()`: ```ts import { databricks } from '@fabric-harness/databricks'; import { init } from '@fabric-harness/sdk'; const dbx = databricks({ host: process.env.DATABRICKS_HOST!, principal: { kind: 'service-principal', host: process.env.DATABRICKS_HOST!, clientId: process.env.DATABRICKS_CLIENT_ID!, clientSecret: process.env.DATABRICKS_CLIENT_SECRET!, }, model: process.env.DATABRICKS_MODEL, warehouseId: process.env.DATABRICKS_WAREHOUSE_ID, vectorSearch: { index: 'main.knowledge.docs_index', textColumn: 'chunk', idColumn: 'id', }, genie: { spaceId: process.env.DATABRICKS_GENIE_SPACE_ID! }, lakeflow: true, consumption: true, governance: { catalogs: ['main'], stewardAudience: 'data-steward', }, }); const runtime = await init({ modelProvider: dbx.modelProvider, tools: dbx.tools, policy: dbx.policy, store: dbx.store, }); ``` ## Build and deploy ```sh fh build --target databricks-app cd .fabricharness/build/databricks-app databricks bundle validate databricks bundle deploy ``` Use Databricks App resources and secret references instead of committing credentials. The detailed [Databricks App tutorial](/docs/deployment/databricks-app) covers App configuration, Lakebase durability, persistent agents, and the live validation gate. --- # Databricks integrations Canonical: https://harness.fabric.pro/docs/databricks/integrations Complete map of Fabric Harness integrations for Databricks data, AI, orchestration, state, governance, telemetry, and cost. `@fabric-harness/databricks` exposes composable factories and a `databricks()` bundle. Use the bundle for the common governed stack; use individual exports when you need a narrower integration. To scaffold project wiring (managed files, env stubs, dependencies), use **[Databricks recipes](/docs/databricks/recipes)**: ```sh fh add databricks sql fh add lakebase fh add vector-search fh add lakeflow ``` ## Data and AI | Service | Fabric Harness API | Agent use | | --- | --- | --- | | Unity AI Gateway | `databricksFoundationModelProvider()`, `ensureDatabricksAiGateway()`, `listDatabricksModelServices()` | Discover and invoke `system.ai.*` model services with submission/tenant request tags | | Model Serving | `databricksFoundationModelProvider({ mode: 'serving-endpoints' })` | Custom serving-endpoint inference | | SQL Warehouse | `databricksSqlTool()` | Parameterized, policy-visible SQL statements | | Unity Catalog | `unityCatalogTablesTool()`, `databricksTableInfoTool()` | Discover governed tables and metadata | | Vector Search | `databricksVectorSearch()`, `databricksRagChain()` | Retrieval for managed- or self-managed-embedding indexes; cookbook online chain | | RAG quality | `scoreRagTurn()`, MLflow 3 export, managed evaluation Job | Local smoke checks plus a UC evaluation dataset and Databricks managed relevance, groundedness, sufficiency, and correctness judges | | Embeddings | `databricksEmbeddings()` | Query embeddings through a serving endpoint | | AI Functions | `databricksAiQueryTool()` | Invoke `ai_query()` through a SQL Warehouse | | AI/BI Genie | `databricksGenieTool()` | Natural-language analytics over a configured Genie space | | Feature Serving | `databricksFeatureLookupTool()` | Low-latency governed feature lookup | | Workspace files | `databricksWorkspaceSource()` | Read-only agent context from workspace files | ## Inference names and URLs Use the workspace **origin** for `DATABRICKS_HOST`, not an API path. Fabric selects the inference base from the model name: | Model value | Mode | OpenAI-compatible base URL | | --- | --- | --- | | `system.ai.gpt-oss-20b` (or another discovered `system.ai.*` service) | Unity AI Gateway | `${DATABRICKS_HOST}/ai-gateway/mlflow/v1` | | A custom endpoint name such as `support-agent-prod` | Custom Model Serving | `${DATABRICKS_HOST}/serving-endpoints` | `DATABRICKS_INFERENCE_MODE=auto` is the default. Set `ai-gateway` or `serving-endpoints` only when overriding automatic routing. Use `DATABRICKS_AI_GATEWAY_BASE_URL` for a proxy or explicitly configured Gateway base. Discover services enabled in the current workspace with `listDatabricksModelServices(client)` instead of assuming every `system.ai.*` service is available. ## Engineering and operations | Service | Fabric Harness API | Agent use | | --- | --- | --- | | Jobs | `databricksRunJobTool()` | Start an existing Databricks Job with idempotent retries | | Notebooks | `databricksNotebookTool()` | Submit a one-time notebook run | | Lakeflow | `databricksLakeflowTools()` | List, inspect, start, and stop pipelines | | MLflow runs | metric and parameter tools | Write run metadata | | MLflow tracing | `mlflowTraceExporter()` | Export settled agent spans to MLflow Tracing | | Serving usage | `servingUsageCapture()` | Associate inference-table usage with submissions | | System tables | `databricksConsumption()` | Aggregate billable usage for tenants and agents | | Actual-cost budgets | `databricksActualCostSource()`, `databricksTenantCostLimit()` | Reconcile policy budgets against usage records | | Lakebase | `lakebaseClient()`, `databricksPersistence()` | Sessions, submissions, conversation streams, and telemetry | | UC Volumes | `UcVolumesAttachmentStore`, Volume source/writer | Governed attachments and non-tabular context | ## Add a standalone tool ```ts import { DatabricksRestClient, databricksFeatureLookupTool, databricksIdentity, withGovernance, } from '@fabric-harness/databricks'; const identity = databricksIdentity({ kind: 'service-principal', host: process.env.DATABRICKS_HOST!, clientId: process.env.DATABRICKS_CLIENT_ID!, clientSecret: process.env.DATABRICKS_CLIENT_SECRET!, }); const client = new DatabricksRestClient({ host: process.env.DATABRICKS_HOST!, token: identity, }); const customerFeatures = withGovernance( databricksFeatureLookupTool(client, { endpoint: 'customer-features', name: 'lookup_customer_features', }), { principal: `sp:${process.env.DATABRICKS_CLIENT_ID}`, onLineage: (record) => auditSink.write(record), }, ); ``` ## Integration boundaries - Fabric Harness does not replace Unity Catalog permissions or Databricks resource ACLs. - The SQL tool and SQL sandbox execute statements; they do not provide a Linux shell. - Workspace files and Unity Catalog Volumes are separate APIs and path spaces. - `databricks-serving` is a proxy target, while `databricks-app` hosts the Node runtime. - Availability and API behavior can vary by cloud, region, workspace feature enablement, and preview status. Validate each enabled integration in the target workspace. --- # Databricks compute patterns Canonical: https://harness.fabric.pro/docs/databricks/compute Choose SQL Warehouses, Lakeflow Jobs and notebooks, Databricks Apps, or an isolated sandbox without conflating their execution models. Databricks offers several execution surfaces. Fabric Harness keeps them explicit so policy, identity, idempotency, and output handling match the workload. ```mermaid flowchart TD Q{What must execute?} Q -->|Governed SQL| SQL[SQL Warehouse] Q -->|Existing workflow| JOB[Lakeflow Job] Q -->|One-off notebook| NB[Notebook task] Q -->|Fabric HTTP runtime| APP[Databricks App] Q -->|General shell or untrusted code| SB[External isolated sandbox] SQL --> UC[Unity Catalog permissions and lineage] JOB --> VOL[Logs and output envelope in UC Volumes] NB --> VOL APP --> LB[Lakebase durable state] SB --> NET[Container, cluster, or provider egress boundary] classDef decision fill:#fff4d6,stroke:#d97706,color:#451a03 classDef compute fill:#e8f0fe,stroke:#2563eb,color:#172554 classDef governed fill:#dcfce7,stroke:#16a34a,color:#052e16 class Q decision class SQL,JOB,NB,APP,SB compute class UC,VOL,LB,NET governed ``` | Workload | Fabric API | Durable result | Policy effect | | --- | --- | --- | --- | | SQL statement | `databricksSqlTool()` or `databricksSqlSandbox()` | Statement id/result | `execute`; gate mutations | | Existing Job | `databricksJobs().runJob()` | Typed run receipt and state | `execute`; use delivery id as idempotency token | | Notebook | `databricksJobs().submitNotebook()` | Typed run receipt and task output | `execute`; gate data mutations | | Agent hosting | `fh build --target databricks-app` | Lakebase session/submission stream | HTTP runtime, not a shell | | General code | Docker, Kubernetes, E2B, Daytona, Modal, or another sandbox | Provider-specific snapshot/ref | Enforce filesystem, process, and network boundaries | ## Run and monitor a Job ```ts import { createDatabricksClient, databricksJobs } from '@fabric-harness/databricks'; const client = createDatabricksClient({ host: process.env.DATABRICKS_HOST!, token: process.env.DATABRICKS_TOKEN!, }); const jobs = databricksJobs(client); const receipt = await jobs.runJob({ jobId: Number(process.env.DATABRICKS_JOB_ID), idempotencyToken: submission.id, notebookParams: { customer: tenant.id }, }); const state = await jobs.wait(receipt.runId, { timeoutMs: 15 * 60_000, signal: abortController.signal, }); if (state.resultState !== 'SUCCESS') { throw new Error(`Job ${receipt.runId} ended in ${state.resultState}`); } ``` The caller-supplied idempotency token survives HTTP retries and upstream message redelivery. Calling `cancel(runId)` is safe to retry. `wait()` returns typed lifecycle/result state and throws `DatabricksRunTimeoutError` rather than returning an ambiguous running result after its deadline. ## Submit a notebook ```ts const receipt = await jobs.submitNotebook({ notebookPath: '/Workspace/Shared/fabric/daily-report', existingClusterId: process.env.DATABRICKS_CLUSTER_ID!, idempotencyToken: submission.id, baseParameters: { report_date: '2026-07-09' }, }); ``` Notebook submission is asynchronous Jobs compute. It does not turn the SQL sandbox into a shell and does not execute TypeScript inside Model Serving. ## Persist logs and outputs in a UC Volume ```ts import { UcVolumesAttachmentStore } from '@fabric-harness/databricks'; const store = new UcVolumesAttachmentStore({ host: process.env.DATABRICKS_HOST!, token: process.env.DATABRICKS_TOKEN!, catalog: 'main', schema: 'agents', volume: 'fabric_attachments', rootPrefix: 'job-output', }); const ref = await jobs.exportOutput(receipt.runId, store, `submission:${submission.id}`); ``` For multi-task Jobs, `getOutputs()` retrieves each task run output. `exportOutput()` stores one content-addressed JSON envelope with notebook result, driver logs, truncation state, errors, and raw metadata. Unity Catalog controls access to the Volume. The [runnable compute example](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-compute) uses a deterministic mock by default and switches to a real workspace when credentials are present. --- # Databricks connectors and sandboxes Canonical: https://harness.fabric.pro/docs/databricks/sandboxes-connectors Choose between the Databricks SQL sandbox, Unity Catalog Volume connectors, workspace sources, attachment storage, and general-purpose compute sandboxes. Databricks appears in both the sandbox and connector layers, but those layers solve different problems. Select them by capability rather than by provider name. ```mermaid flowchart TD NEED{What must the agent do?} NEED -->|Run governed SQL| SQL[Databricks SQL sandbox or SQL tool] NEED -->|Read or write governed files| VOL[Unity Catalog Volume connector] NEED -->|Read workspace source files| WS[Workspace Files source] NEED -->|Persist user attachments| ATT[UC Volumes attachment store] NEED -->|Run bash, packages, or arbitrary code| GEN[Docker or remote code sandbox] SQL --> WH[SQL Warehouse] VOL --> FILES[Files API] WS --> WAPI[Workspace API] ATT --> FILES GEN --> DATA[Call Databricks tools over governed APIs] classDef question fill:#fef3c7,stroke:#d97706,color:#422006 classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef dbx fill:#dcfce7,stroke:#16a34a,color:#052e16 class NEED question class SQL,VOL,WS,ATT,GEN fabric class WH,FILES,WAPI,DATA dbx ``` ## SQL sandbox `databricksSqlSandbox()` adapts `session.shell(sql)` to SQL Statement Execution on a SQL Warehouse. It returns JSONL or CSV in `stdout`. Its small filesystem is in-memory session storage; it is not DBFS, a Volume, a cluster driver, or an operating-system shell. ```ts import { databricksSqlSandbox } from '@fabric-harness/databricks/sql-sandbox'; import { init } from '@fabric-harness/sdk'; const runtime = await init({ sandbox: databricksSqlSandbox({ host: process.env.DATABRICKS_HOST!, token: async () => workspaceIdentity(), warehouseId: process.env.DATABRICKS_WAREHOUSE_ID!, catalog: 'main', schema: 'analytics', resultFormat: 'jsonl', }), }); const session = await runtime.session(); const result = await session.shell(` SELECT region, sum(net_revenue) AS revenue FROM orders GROUP BY region ORDER BY revenue DESC `); ``` Use `databricksSqlTool()` instead when the model should call a named SQL tool alongside other tools. The sandbox is useful when the session's shell abstraction should itself mean SQL. ## Unity Catalog Volume connector The connector uses the Databricks Files API and keeps all paths under a configured `/Volumes///` root. ```ts import { databricksVolumeSource } from '@fabric-harness/connectors/databricks-volume'; const source = databricksVolumeSource({ host: process.env.DATABRICKS_HOST!, token: async () => workspaceIdentity(), volumePath: '/Volumes/main/support/knowledge', include: (path) => path.endsWith('.md') || path.endsWith('.pdf'), }); await session.mount('/knowledge', source); ``` `databricksVolumeWriter()` provides scoped `put()` and `delete()` operations. Use `UcVolumesAttachmentStore` when attachments must participate in Fabric's attachment lifecycle. The acting principal still needs the relevant `USE CATALOG`, `USE SCHEMA`, and Volume privileges. ## General-purpose code execution For Python, package installation, bash, repository mutation, or untrusted code, use a code sandbox such as Docker, Kubernetes, E2B, Daytona, Modal, or another remote backend. Give that sandbox Databricks tools or scoped OAuth access as needed. This preserves the common sandbox interface without pretending a SQL Warehouse is a machine shell. See the focused [Databricks SQL sandbox reference](/docs/ecosystem/sandboxes/databricks-sql) and the [sandbox matrix](/docs/reference/sandboxes-matrix). --- # Enterprise Databricks controls Canonical: https://harness.fabric.pro/docs/databricks/enterprise Identity propagation, Unity Catalog enforcement, approvals, audit lineage, durable state, cost controls, secret handling, and deployment hardening. Fabric Harness adds runtime controls around Databricks calls. Those controls are defense in depth: Unity Catalog and Databricks resource permissions remain authoritative. ## Identity modes | Mode | Use | Fabric API | | --- | --- | --- | | App service principal | Databricks Apps and unattended production workloads | `appServicePrincipalFromEnv()` | | OAuth M2M | External services acting as one application | `databricksIdentity({ kind: 'service-principal' })` | | On-behalf-of user | Preserve the signed-in user's grants | `onBehalfOfFromHeaders()` | | Personal access token | Local development or controlled single-user testing | `kind: 'pat'` | Token providers are resolved at call time and refresh before expiry. Identity labels, not tokens, are placed in actors, submissions, lineage, and cost records. For Databricks Apps ingress, use `databricksAppsOidcAuthenticator()` as the Node server `authenticate` hook. It validates workspace JWT signatures, issuer, audience, expiry, and signing-key rotation before mapping the forwarded user email to both the Fabric actor and Unity Catalog principal. See [Authentication and RBAC](/docs/operating/auth#databricks-apps). ## Enforcement flow ```mermaid flowchart LR R[Tool request] --> A[Definition and invocation policy] A --> E{Allowed?} E -->|No| DENY[Reject and audit] E -->|Yes| H{Approval required?} H -->|Yes| WAIT[Durable approval wait] WAIT --> DEC{Decision} DEC -->|Deny or expire| DENY DEC -->|Approve| CALL[Call Databricks] H -->|No| CALL CALL --> UC[Unity Catalog and resource ACLs] UC -->|Denied| DENY UC -->|Allowed| RESULT[Redacted result] RESULT --> LINEAGE[Lineage, telemetry, and cost] classDef decision fill:#fef3c7,stroke:#d97706,color:#422006 classDef allow fill:#dcfce7,stroke:#16a34a,color:#052e16 classDef deny fill:#fee2e2,stroke:#dc2626,color:#450a0a class E,H,DEC decision class CALL,UC,RESULT,LINEAGE allow class DENY deny ``` ## Controls to configure 1. Use a least-privilege service principal or a user OBO token for each request. 2. Restrict outbound network policy to the workspace host and any explicitly required services. 3. Add catalog allowlists as defense in depth; do not treat them as a substitute for UC grants. 4. Route write and execute tools to a data-steward approval audience. 5. Persist sessions, submissions, and streams in Lakebase for restart recovery. 6. Send governed tool lineage to an audit sink, MLflow, OpenTelemetry, or Lakebase telemetry tables. 7. Apply estimated and actual-cost tenant budgets, then reconcile against system-table usage. 8. Keep secrets in Databricks App resources, environment-backed secret references, or an external secret manager. Never put credentials in prompts, tool inputs, or lineage labels. 9. Build with provenance and SBOM output, inspect the v2 manifest, and require an API token on production artifact routes. ## Lakebase credential exchange `lakebaseClient()` does not use a workspace OAuth token as the Postgres password. It exchanges the workspace token for a database credential using the endpoint resource name, refreshes that credential early with jitter, single-flights concurrent refreshes, and supplies it to the connection pool. ```ts const app = databricksApp(); const server = await startDevServer({ host: '0.0.0.0', port: Number(process.env.DATABRICKS_APP_PORT ?? 8080), ...(await app.serverOptions()), }); ``` `serverOptions()` injects the Lakebase session, submission, and conversation-stream stores into the shared Node server. See [Lakebase](/docs/ecosystem/databases/lakebase) for configuration. --- # Workspace compatibility Canonical: https://harness.fabric.pro/docs/databricks/compatibility Evidence-based Databricks cloud, region, authentication, API, App, Lakebase, and runtime compatibility. Fabric Harness targets Databricks workspaces on AWS, Azure, and Google Cloud through workspace REST APIs. Availability of Apps, Lakebase, serverless Model Serving, private connectivity, and individual AI services varies by workspace, region, and account configuration. A successful check in one workspace is recorded only for that cloud and region. ## Runtime and API contract | Surface | Version used | | --- | --- | | Lakeflow Jobs | REST 2.1 | | SQL Statement Execution | REST 2.0 | | Unity Catalog | REST 2.1 | | Workspace and MLflow | REST 2.0 | | Lakebase credential exchange | REST 2.0 | | Databricks Apps | REST 2.0 / CLI project deployment | | Node.js | `>=20.18.0` | | `@fabric-harness/databricks` | `>=1.1.0 <2` | | `pg` for Lakebase | `^8.11.0` | The constants are exported as `DATABRICKS_API_VERSIONS`, `DATABRICKS_AUTH_MODES`, and `DATABRICKS_PACKAGE_COMPATIBILITY` so deployment checks and documentation use the same values. ## Authentication modes | Mode | Use | | --- | --- | | OAuth M2M | External services and protected CI service principals | | App service principal | Runtime identity injected into a Databricks App | | On-behalf-of | Per-user Unity Catalog grant enforcement from an App request | | PAT | Developer smoke tests and transitional automation | Production deployments should use OAuth or App identity. Tokens remain in resolvers and request headers; they are not written to prompts, lineage, compatibility records, or certification errors. ## Certification records The protected live workflow emits schema-versioned, redacted evidence containing: - cloud, region, workspace id/host, auth mode, and principal kind; - Node and package versions plus every REST API family used; - required and optional check identifiers with duration and result; - source commit and SHA-256 digest of the exact App artifact; - App restart/cascade-deletion evidence and deployment conformance; - a stable evidence id suitable for partner review. Required checks cannot become `not-configured`: that makes certification fail. The default partner set covers identity, SQL allow/deny, Unity Catalog preflight, mutation approval, Model Serving, ChatAgent proxy, Vector Search, MLflow managed RAG evaluation, Genie, Feature Serving, Lakeflow, Jobs, notebook execution, Volumes, Lakebase, lineage, OBO grant differences, System Tables, cost reconciliation, and the App. ```bash node packages/databricks/dist/certify.js node scripts/build-databricks-compatibility-matrix.mjs \ artifacts/databricks-certification.json ``` `databricksCompatibilityRecord()` accepts only passing evidence with cloud, region, auth, runtime, package, and artifact identity. The matrix labels a cloud/region `live-certified` only when such a record is retained; otherwise it remains a contract target requiring workspace certification. ## App and Lakebase prerequisites For the reference App, enable Databricks Apps, grant its service principal the selected SQL Warehouse/serving/UC resources, provision the scoped runtime-token secret, and configure a UC Volume for attachments. Restart recovery additionally requires a Lakebase Autoscaling endpoint and database credential-exchange permission. Apply [private networking](/docs/operating/private-networking) when the workspace uses private connectivity or an enterprise CA. --- # Brickbuilder submission readiness Canonical: https://harness.fabric.pro/docs/databricks/submission-readiness Evidence checklist for preparing Fabric Harness for a Databricks Brickbuilder solution or accelerator technical review. This page is an engineering evidence checklist for a Brickbuilder submission, not a claim of Databricks validation, endorsement, or program acceptance. Confirm the current process and requirements in the Databricks Partner Portal with your partner contact. ## Solution narrative Fabric Harness is a TypeScript agent runtime and deployment toolkit for governed data and AI agents. Its Databricks value is the integration of Model Serving, Unity Catalog-aware tools, SQL Warehouses, Vector Search, AI/BI Genie, Feature Serving, Lakeflow, Lakebase durability, MLflow telemetry, usage reconciliation, approval controls, and deployable Databricks App artifacts under one propagated identity. ## Technical evidence | Evidence | What to provide | | --- | --- | | Repeatable install | Databricks template scaffold and locked package versions | | Reference architecture | [Architecture and identity flow](/docs/databricks/architecture) | | Live App | A deployed `databricks-app` artifact with health and readiness checks | | Governed denial | A recording or test showing Unity Catalog rejects an unauthorized table or volume | | App identity | Service-principal execution with token values absent from logs and model context | | User identity | OBO request showing user-level permissions when the use case requires it | | Durable recovery | Lakebase-backed session and submission recovery after App restart | | AI integrations | Live Model Serving plus at least one of Vector Search, Genie, or Feature Serving | | Operational evidence | MLflow or OpenTelemetry trace correlated to submission and tenant identifiers | | Cost governance | Estimated budget enforcement and actual usage reconciliation | | Supply chain | Manifest, SBOM, provenance, dependency scan, and Apache-2.0 license | | Test report | Unit, contract, artifact, security, and live workspace test results with dates | ## Live validation gate Create or reconcile the isolated certification fixture with a workspace-admin user token. Review the plan before allowing mutations: ```sh pnpm databricks:cert:plan FABRIC_DATABRICKS_PROVISION=1 pnpm databricks:cert:provision ``` The provisioner is safe to rerun. It creates named, disposable resources for SQL, UC allow/deny, Vector Search, MLflow evaluation, Jobs, Lakeflow, Genie, Feature Serving, Volumes, and OBO grant differentiation, then writes the environment-variable manifest under `artifacts/`. ```sh pnpm --filter @fabric-harness/databricks build FABRIC_DATABRICKS_EVIDENCE_PATH=artifacts/databricks-certification.json \ node packages/databricks/dist/certify.js node scripts/build-databricks-compatibility-matrix.mjs artifacts/databricks-certification.json ``` The command records each capability as `passed`, `failed`, or `not-configured`, fails when any required capability does not pass, removes tokens from errors, and writes schema-versioned JSON with commit SHA, durations, workspace origin, and summary counts. The `databricks-live` GitHub environment uploads that file as the `databricks-certification` workflow artifact. Core variables: | Capability | Configuration | | --- | --- | | Identity | `DATABRICKS_HOST` plus `DATABRICKS_TOKEN`, or `DATABRICKS_CLIENT_ID` and `DATABRICKS_CLIENT_SECRET` for OAuth M2M | | SQL and Unity Catalog | `DATABRICKS_WAREHOUSE_ID`, `DATABRICKS_CATALOG`, `DATABRICKS_SCHEMA` | | Governed SQL | `DATABRICKS_ALLOWED_TABLE` and `DATABRICKS_DENIED_TABLE` | | Unity AI Gateway model service | `DATABRICKS_MODEL` (`system.ai.*`) | | Optional custom endpoint | `DATABRICKS_MODEL` plus `DATABRICKS_INFERENCE_MODE=serving-endpoints` | | Vector Search and RAG | `DATABRICKS_VECTOR_INDEX`, `DATABRICKS_VECTOR_TEXT_COLUMN`, `DATABRICKS_VECTOR_ID_COLUMN`, `DATABRICKS_RAG_QUERY` | | Managed RAG evaluation | `DATABRICKS_RAG_EVAL_JOB_ID`; optional `DATABRICKS_RAG_EVAL_THRESHOLD` on the Job environment | | Genie | `DATABRICKS_GENIE_SPACE_ID` | | Feature Serving | `DATABRICKS_FEATURE_ENDPOINT`, `DATABRICKS_FEATURE_RECORDS_JSON` | | Lakeflow | `DATABRICKS_PIPELINE_ID` | | Jobs and notebook | `DATABRICKS_JOB_ID`, `DATABRICKS_NOTEBOOK_PATH`; `DATABRICKS_CLUSTER_ID` only for classic compute | | UC Volumes | `DATABRICKS_VOLUME` plus catalog/schema | | Lakebase | `ENDPOINT_NAME`, `PGHOST`, `PGDATABASE`, `PGUSER` | | Managed App Lakebase resource | `BUNDLE_VAR_lakebase_endpoint`, `BUNDLE_VAR_lakebase_database_resource` | | OBO | `DATABRICKS_OBO_TOKEN`, `DATABRICKS_OBO_DIFFERENTIAL_TABLE`, `DATABRICKS_OBO_EXPECT` | | System Tables and reconciliation | `DATABRICKS_SYSTEM_TABLES_TEST=1`, cost scope/estimate/drift variables, plus warehouse | | Databricks App | `DATABRICKS_APP_URL` | | ChatAgent proxy | `DATABRICKS_CHAT_AGENT_ENDPOINT` | Keep the existing live Vitest suite enabled as a lower-level diagnostic gate. Certification is the strict evidence gate: optional checks may be unconfigured, but required checks cannot silently skip. The protected `databricks-live` workflow runs daily, on every published release, and on demand. OBO is an explicit manual-dispatch gate because the user authorization token is intentionally short-lived; refresh `DATABRICKS_OBO_TOKEN` and dispatch with `require_obo` enabled. For a second workspace or cloud, create another protected GitHub Environment with the same secret and variable names, provision that workspace with `pnpm databricks:cert:provision`, then dispatch `live-tests.yml` with `databricks_environment` set to the new environment name. The workflow runs the same build, App deployment, restart, load, RAG, governance, Model Serving, and evidence gates; it does not infer compatibility from the first workspace. Use `run_soak` for the 15-minute paced load gate. The protected Azure reference run for commit `9add3f89d59c45d806919b5329759df997903728` completed the paced gate with 300 of 300 requests, no failures, and no duplicate run IDs. Admission p95 was `81 ms` against a `1,500 ms` limit, and end-to-end p95 was `339 ms` against a `10,000 ms` limit. The same run passed all 24 required Databricks capability checks plus App health, readiness, and finite-job lifecycle conformance. Treat these as reference-workspace evidence, then run the gate with your own App workload, capacity, data, and SLO thresholds before production approval. The workflow covers: - Databricks App project deployment, bare stop/start recovery from the same source into a new snapshot, managed-resource continuity, and cascade deletion. - Service-principal and OBO identity paths used by the solution. - Unity AI Gateway readiness, model-service discovery, tagged inference, and secured MLflow ChatAgent aggregated and streaming proxy invocation. - Vector Search retrieval plus a grounded RAG answer with validated inline citations. - A governed MLflow 3 evaluation dataset and serverless managed-judge quality gate for answer and retrieval quality. - SQL Warehouse execution and permission denial. - Unity Catalog table and Volume allow and deny cases. - Lakebase credential exchange plus session, submission, conversation offset, and attachment recovery. - Every optional API enabled in the submitted reference solution. - API authentication, mutation approval classification, lineage joins, and actual-cost reconciliation. The interactive OBO lifecycle is intentionally separate from unattended release CI because it must cross a real user-token expiry boundary. Run `scripts/certify-databricks-obo-lifecycle.mjs prepare` and `verify` with one U2M CLI profile; retain `artifacts/databricks-obo-lifecycle.json` with the release evidence. The artifact contains only identity stability and token lifetime timestamps. ## Official guidance - [Databricks Partner Well-Architected Framework](https://databrickslabs.github.io/partner-architecture/) - [Databricks Apps authorization](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/auth) - [Add a Lakebase resource to a Databricks App](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/lakebase) - [Unity Catalog Volumes](https://docs.databricks.com/aws/en/volumes/) - [Authorize access to Databricks resources](https://docs.databricks.com/aws/en/dev-tools/auth) Do not use Databricks program badges or validation language until Databricks has granted that status. --- # Databricks App Tutorial Canonical: https://harness.fabric.pro/docs/deployment/databricks-app Scaffold, test, build, deploy, and persist a Fabric Harness agent on Databricks Apps. This tutorial deploys a finite analytics job into Databricks Apps. The App service principal calls Model Serving and Unity Catalog APIs, while optional Lakebase stores sessions, durable submissions, and conversation streams. Read the [Databricks architecture](/docs/databricks/architecture) first when evaluating identity, governance, or service boundaries. Use this page for the scaffold-to-deployment procedure. ## Prerequisites - Node.js 22+ and pnpm or npm. - A Databricks workspace with Apps enabled. - A Model Serving endpoint. - A SQL warehouse for the generated analytics tools. - Optional: a Lakebase Autoscaling endpoint and OAuth Postgres role. ## 1. Scaffold the project ```sh npx @fabric-harness/cli init --template databricks --dir analytics-agent cd analytics-agent npm install ``` The template creates: ```text .fabricharness/ jobs/databricks-analyst.ts roles/data-analyst.md skills/analyze-table/SKILL.md config.ts .env.example AGENTS.md package.json ``` The generated job uses `defineDatabricksAgent()`: ```ts title=".fabricharness/jobs/databricks-analyst.ts" import { defineDatabricksAgent } from '@fabric-harness/databricks'; import { schema } from '@fabric-harness/sdk'; export default defineDatabricksAgent({ name: 'databricks-analyst', description: 'Answer questions using SQL and Unity Catalog.', input: schema.object({ question: schema.string() }), output: schema.string(), triggers: { webhook: true, manual: true }, model: 'system.ai.gpt-oss-20b', tools: ['sql', 'tables', 'table-info'], sandbox: 'local', }); ``` ## 2. Run without credentials The mock path validates discovery, schemas, tool assembly, and the model loop without contacting a workspace: ```sh fh agents fh describe databricks-analyst fh run databricks-analyst \ --question "What tables are available in main?" \ --mock ``` Mock mode does not validate OAuth scopes, Unity Catalog grants, SQL execution, or deployment. ## 3. Configure a live workspace Copy the template and fill the required values: ```sh cp .env.example .env.local ``` ```dotenv title=".env.local" DATABRICKS_HOST=https:// DATABRICKS_CLIENT_ID=00000000-0000-0000-0000-000000000000 DATABRICKS_CLIENT_SECRET=replace-with-secret-reference DATABRICKS_WAREHOUSE_ID=0123456789abcdef DATABRICKS_MODEL=system.ai.gpt-oss-20b ``` Run a live source invocation: ```sh fh run databricks-analyst --question "Describe main.sales.orders" ``` The service principal must have workspace access, permission to invoke the serving endpoint, warehouse usage, and the required Unity Catalog grants. ## 4. Build the App ```sh fh build --target databricks-app ``` Expected summary: ```text Build complete Output .fabricharness/build/databricks-app Manifest .fabricharness/build/databricks-app/manifest.json Jobs databricks-analyst Agents none ``` Inspect the v2 manifest before deployment: ```sh jq '{schemaVersion, jobs, agents, entrypoint}' \ .fabricharness/build/databricks-app/manifest.json ``` ```json { "schemaVersion": 2, "jobs": [{ "name": "databricks-analyst", "kind": "job" }], "agents": [], "entrypoint": "dist/server.mjs" } ``` The artifact includes `app.yaml`, `databricks.yml`, the shared v2 Node server, and bundled `.mjs` definitions. It does not regenerate a separate legacy HTTP runtime. ## 5. Deploy with an Asset Bundle ```sh cd .fabricharness/build/databricks-app databricks auth login --host "$DATABRICKS_HOST" databricks bundle validate databricks bundle deploy ``` Set provider and resource configuration through Databricks App environment/resources rather than committing secrets. Apps supplies `DATABRICKS_APP_PORT`; the generated `app.yaml` starts `dist/server.mjs` on that port. For a configuration-preserving production restart, pass the App name explicitly so the Databricks CLI uses the Apps API and restarts the existing active deployment: ```sh databricks apps stop "$DATABRICKS_APP_NAME" databricks apps start "$DATABRICKS_APP_NAME" ``` Running `databricks apps start` without a name inside the generated bundle directory enters project mode and resolves bundle variables again. Use the explicit-name form for a restart, or rerun `fh deploy --target databricks-app` when applying new source or configuration. The protected live workflow verifies the actual platform behavior: a bare stop/start creates a new deployment snapshot from the same configured source path. It then proves that the managed App resources, pending human approval, Lakebase sessions, submissions, conversation offsets, and UC Volume attachments remain usable after the restart. Static, non-secret workspace settings are emitted into `app.yaml`, so the new snapshot retains its catalog, schema, volume, model endpoint, and SQL warehouse configuration. Once the App is running, invoke the finite job through its App URL: ```sh curl -sS "$DATABRICKS_APP_URL/jobs/databricks-analyst" \ -H "authorization: Bearer $DATABRICKS_TOKEN" \ -H 'content-type: application/json' \ -d '{"question":"What were yesterday’s top products?"}' ``` ## 6. Add Lakebase durability Fabric needs both Postgres connection information and the full Lakebase endpoint resource name: ```dotenv DATABRICKS_LAKEBASE_HOST=ep-id.database.us-west-2.cloud.databricks.com DATABRICKS_LAKEBASE_DATABASE=databricks_postgres DATABRICKS_LAKEBASE_USER=00000000-0000-0000-0000-000000000000 DATABRICKS_LAKEBASE_ENDPOINT=projects/project-id/branches/branch-id/endpoints/endpoint-id PGPORT=5432 ``` The scaffold includes the `pg` dependency. At runtime Fabric: 1. Gets a workspace OAuth token from the App service principal. 2. Exchanges it at `POST /api/2.0/postgres/credentials` using the endpoint resource name. 3. Supplies the database credential through the pool password callback. 4. Refreshes before expiry and single-flights concurrent refreshes. 5. Injects Lakebase session, submission, and conversation-stream stores into the shared server. Never place the workspace OAuth token directly in `PGPASSWORD`. For `fh deploy`, provide the Lakebase **resource names** to the bundle separately from the direct PostgreSQL connection values shown above: ```sh export BUNDLE_VAR_lakebase_endpoint='projects/project-id/branches/production/endpoints/primary' export BUNDLE_VAR_lakebase_database_resource='projects/project-id/branches/production/databases/app-db' fh deploy --target databricks-app ``` `app-db` is the Lakebase database resource ID. It can differ from the PostgreSQL database name such as `databricks_postgres`. Use the `name` field returned by `databricks postgres list-databases projects/project-id/branches/production`. For a custom server entrypoint, the equivalent wiring is: ```ts import { databricksApp } from '@fabric-harness/databricks'; import { startDevServer } from '@fabric-harness/node'; const app = databricksApp(); const server = await startDevServer({ port: Number(process.env.DATABRICKS_APP_PORT ?? 8080), host: '0.0.0.0', ...(await app.serverOptions()), }); const shutdown = async () => { await server.close(); await app.close(); }; process.once('SIGTERM', () => void shutdown()); process.once('SIGINT', () => void shutdown()); ``` ## 7. Add a persistent agent Create `.fabricharness/agents/copilot.ts`: ```ts import { defineAgent } from '@fabric-harness/sdk'; export default defineAgent(({ id }) => ({ name: 'copilot', model: 'databricks/system.ai.gpt-oss-20b', instructions: `You are the governed analytics copilot for account ${id}.`, triggers: { webhook: true }, })); ``` Rebuild. The manifest now reports the finite job under `jobs` and `copilot` under `agents`. Persistent input returns a durable receipt: ```sh curl -i "$DATABRICKS_APP_URL/agents/copilot/acct-42" \ -H "authorization: Bearer $DATABRICKS_TOKEN" \ -H 'content-type: application/json' \ -d '{"message":"Summarize this account’s sales."}' ``` Use `@fabric-harness/client` to wait or stream by offset. See [Persistent agents](/docs/building/persistent-agents). ## 8. Validate before production Run the repository live suite with a controlled workspace: ```sh FABRIC_DATABRICKS_TEST=1 \ FABRIC_DATABRICKS_LAKEBASE_TEST=1 \ pnpm --filter @fabric-harness/databricks test ``` The production gate should cover App deployment, service-principal access, user OBO where used, Unity Catalog denials, Model Serving, SQL, and Lakebase restart recovery. Local contract tests do not replace that workspace-specific validation. ## See also - [Databricks integration reference](/docs/deployment/databricks) - [Build and run artifacts](/docs/deployment/build-artifacts) - [Enterprise controls](/docs/building/enterprise-controls) - [Production readiness](/docs/reference/production-readiness) --- # Databricks SQL sandbox Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/databricks-sql Use a Databricks SQL Warehouse as a governed query execution sandbox for Fabric Harness sessions. **Package:** `@fabric-harness/databricks/sql-sandbox` **Execution:** Databricks SQL Statement Execution API `databricksSqlSandbox()` maps `session.shell(statement)` to a SQL Warehouse. It is designed for data agents whose shell abstraction should mean SQL, while retaining the common Fabric Harness sandbox interface. ```ts import { databricksSqlSandbox } from '@fabric-harness/databricks/sql-sandbox'; import { init } from '@fabric-harness/sdk'; const runtime = await init({ sandbox: databricksSqlSandbox({ host: process.env.DATABRICKS_HOST!, token: async () => workspaceIdentity(), warehouseId: process.env.DATABRICKS_WAREHOUSE_ID!, catalog: 'main', schema: 'analytics', resultFormat: 'jsonl', waitTimeoutSeconds: 30, }), }); const session = await runtime.session(); const result = await session.shell('SELECT current_user(), current_catalog()'); ``` ## Capabilities | Capability | Behavior | | --- | --- | | `exec()` | Executes one SQL statement and serializes result rows as JSONL or CSV | | Text and binary files | Small in-memory map scoped to the sandbox instance | | Snapshots and restore | Not supported | | Network | Managed by the provider | | Persistence | Session only for the in-memory file map | | Isolation | Databricks SQL Warehouse plus its configured Unity Catalog access | The file methods do not access DBFS, workspace files, or Unity Catalog Volumes. Mount a `databricksVolumeSource()` for governed non-tabular data. Use Docker or another code sandbox for bash, Python package installation, repository work, or arbitrary code execution. ## Required access The acting principal needs workspace access, permission to use the SQL Warehouse, and the required Unity Catalog grants. Prefer a rotating OAuth token provider rather than a long-lived token. See [Databricks connectors and sandboxes](/docs/databricks/sandboxes-connectors) for the selection guide and full examples. --- # Databricks Lakebase Canonical: https://harness.fabric.pro/docs/ecosystem/databases/lakebase Durable state for Databricks Apps using exchanged database credentials. Lakebase is Fabric's preferred durable Postgres path inside Databricks Apps. Configure the database connection fields and the full endpoint resource name: ```sh DATABRICKS_LAKEBASE_HOST=ep-id.database.us-west-2.cloud.databricks.com DATABRICKS_LAKEBASE_DATABASE=databricks_postgres DATABRICKS_LAKEBASE_USER=00000000-0000-0000-0000-000000000000 DATABRICKS_LAKEBASE_ENDPOINT=projects//branches//endpoints/ PGPORT=5432 ``` `databricksApp()` also accepts `PGHOST`, `PGDATABASE`, `PGUSER`, `PGPORT`, and `ENDPOINT_NAME`, which align with App database resource configuration. The Databricks bundle exchanges the app's workspace OAuth identity for a short-lived database credential, refreshes it with single-flight protection, and injects Lakebase-backed session, submission, and conversation-stream stores into the shared server. A raw workspace token is rejected as a database password. Use `lakebaseClient()` when assembling a custom bundle. For the standard path, follow [Databricks App deployment](/docs/deployment/databricks-app#6-add-lakebase-durability), including the live restart and credential-exchange smoke. Local contract tests cannot validate workspace permissions or the hosted credential response shape. --- # Authentication and RBAC Canonical: https://harness.fabric.pro/docs/operating/auth Bind authenticated principals, tenants, permissions, and SSO identities to every server operation. `fh server` ships a simple Bearer-token auth surface for solo deployments. Enterprise hosts use `authenticate` to return a principal with tenant and permission scopes. The server derives the execution actor from that principal, so clients cannot spoof audit identity in request bodies. Local/dev mode remains open when no auth is configured. Production mode fails closed: only `/health` and `/ready` are anonymous, and every other HTTP or WebSocket route returns `401` unless the bearer token or custom resolver authorizes it. ## Default: Bearer token ```sh FABRIC_HARNESS_API_TOKEN=changeme fh server ``` ```sh curl -H 'Authorization: Bearer changeme' http://localhost:9111/sessions ``` WebSocket upgrades use the same token via `?token=` query param (browsers can't set headers on `WebSocket` constructor): ```ts new WebSocket('wss://app.example.com/sessions/abc/ws?token=changeme'); ``` ## Custom resolver: `extractAuthToken` When you have an existing identity layer (cookies, JWT, SSO terminator), pass a custom resolver: ```ts import { startDevServer, parseCookie } from '@fabric-harness/node'; await startDevServer({ extractAuthToken: (req) => { const cookie = parseCookie(req, 'session'); if (!cookie) return undefined; // fall through to bearer check return verifySessionCookie(cookie); // your validator returns true/false }, }); ``` The resolver returns: - `true` — authorize the request - `false` — reject with 401 - `undefined` — fall through to the built-in bearer-token check Same hook works for HTTP requests AND WebSocket upgrades. ## Enterprise principals and permissions Use `authenticate` when the server must enforce tenant isolation and route-level RBAC: ```ts import { startDevServer } from '@fabric-harness/node'; await startDevServer({ authenticate: async (req) => { const claims = await verifyCompanyJwt(req.headers.authorization); if (!claims) return false; return { id: claims.sub, kind: claims.type === 'user' ? 'user' : 'service-principal', provider: 'company-oidc', tenantId: claims.organizationId, displayName: claims.name, ucPrincipal: claims.databricksPrincipal, permissions: claims.permissions, }; }, }); ``` The built-in permission scopes are: | Permission | Operations | | --- | --- | | `agent:invoke` | Jobs, persistent prompts, dispatch, and channel ingress | | `session:read` | Session, run, timeline, metrics, and conversation reads | | `session:abort` | Abort an active persistent submission | | `session:delete` | Cascade-delete a settled persistent instance | | `approval:read`, `approval:write` | Inspect and vote on approvals | | `artifact:read` | Read artifacts and attachments | | `build:read` | Read build manifests | | `admin:read` | Admin and OpenAPI routes | `*` grants every permission and `session:*` grants all permissions for one resource. Omitting `permissions` keeps compatibility with existing custom authenticators and grants unrestricted access; enterprise authenticators should always return an explicit array. `tenantId` binds the principal to one tenant. A request for a different `X-Fabric-Tenant` or `?tenant=` receives `403`. Use `tenantIds` instead when an operator may explicitly select from a known set. A single-value `tenantIds` allowlist binds implicitly when the request omits a tenant; an allowlist containing multiple tenants requires an explicit permitted tenant selection and otherwise returns `403`. Omitting the selection never widens access to every tenant. The resulting principal and tenant propagate through submissions, entries, approvals, tool attribution, and telemetry. Add application-specific checks after the built-in scopes with `authorize`: ```ts await startDevServer({ authenticate: companyAuthenticator, authorize: ({ principal, permission, tenantId }) => permission !== 'session:delete' || (principal.roles?.includes('tenant-admin') === true && tenantId !== undefined), }); ``` HTTP and WebSocket upgrades use the same authentication, tenant, and authorization pipeline. ## OIDC and JWKS validation Install `jose`, then use the built-in validator for any OpenID Connect provider. It verifies the signature against a local or remote JWKS, issuer, audience, required claims, algorithms, expiry, and clock tolerance. Remote JWKS keys are cached and refreshed when providers rotate signing keys. ```sh pnpm add jose ``` ```ts import { oidcJwtAuthenticator, startDevServer } from '@fabric-harness/node'; await startDevServer({ authenticate: oidcJwtAuthenticator({ issuer: 'https://identity.example.com', audience: 'fabric-api', jwksUri: 'https://identity.example.com/.well-known/jwks.json', provider: 'company-oidc', claims: { tenantId: 'organization_id', roles: 'roles', groups: 'groups', permissions: 'permissions', }, groupRoles: { 'platform-operators': ['operator'], }, rolePermissions: { operator: ['session:*', 'approval:*', 'artifact:read'], }, }), }); ``` The validator returns `undefined` when no supported token is present, so a configured legacy API token can remain as fallback. An invalid or expired JWT returns `false` and the server responds with `401`. Use `mapPrincipal` when provider claims need logic beyond declarative claim mapping. ### Microsoft Entra ID `entraIdAuthenticator()` configures the tenant-specific v2 issuer and rotating JWKS. It maps `oid`, `tid`, `name`, `roles`, and `groups`, and treats `idtyp: app` as a service principal. ```ts authenticate: entraIdAuthenticator({ tenantId: process.env.ENTRA_TENANT_ID!, clientId: process.env.ENTRA_CLIENT_ID!, groupRoles: { [process.env.ENTRA_OPERATOR_GROUP!]: ['operator'] }, rolePermissions: { operator: ['session:*', 'approval:*', 'admin:read'] }, }) ``` ### Databricks Apps `databricksAppsOidcAuthenticator()` validates workspace-issued JWTs and accepts either an `Authorization: Bearer` token or the Databricks Apps `x-forwarded-access-token` header. It maps the workspace, email/Unity Catalog principal, roles, and groups into the same server identity. ```ts authenticate: databricksAppsOidcAuthenticator({ workspaceHost: process.env.DATABRICKS_HOST!, audience: process.env.DATABRICKS_APP_CLIENT_ID!, rolePermissions: { 'data-steward': ['session:read', 'approval:*'] }, }) ``` If the workspace uses a custom issuer, account-level identity federation, or a proxy JWKS, set `issuer` and `jwksUri` explicitly. Keep the service-principal path for unattended traffic and use the forwarded user token only when operations should inherit that user's Databricks grants. ## Delete a persistent instance Deletion is deliberately restricted to settled work: ```sh curl -X DELETE \ -H "Authorization: Bearer $TOKEN" \ https://agents.example.com/agents/support/customer-42 ``` The operation removes every named session for that agent instance, its settled submissions, conversation streams, session artifacts, and content-addressed attachments. If work is still active, the server requests an abort and returns `409`; retry after settlement. Durable custom stores must implement `SessionStore.delete` and `deleteSessionSubmissions`. ## Recipe: SSO terminator (Cloudflare Access, IAP, Cognito) When fabric-harness runs behind an SSO terminator, the terminator validates the user and forwards a verified header. Trust the header inside the resolver: ```ts await startDevServer({ extractAuthToken: (req) => { const userHeader = req.headers['cf-access-authenticated-user-email']; if (typeof userHeader === 'string' && userHeader.length > 0) return true; return undefined; }, }); ``` Make sure the terminator strips the header from inbound requests that didn't pass through it — otherwise clients can spoof. ## Recipe: per-tenant cookies Combine with `X-Fabric-Tenant` header to scope cookie validation per tenant: ```ts extractAuthToken: (req) => { const tenant = req.headers['x-fabric-tenant']; const cookie = parseCookie(req, `session_${tenant}`); return cookie ? verifyForTenant(cookie, tenant) : undefined; }, ``` ## WebSocket cookies (browsers) Same-origin WebSocket upgrades carry browser cookies automatically — `extractAuthToken` reads them just like HTTP. Cross-origin? `Sec-WebSocket-Protocol` workarounds exist but are clunky; recommend bearer-via-query-param (`?token=...`) for cross-origin WS. ## See also - [HTTP server reference](/docs/reference/http-server) - [Multi-tenancy](/docs/operating/multi-tenancy) --- # Operator Console Canonical: https://harness.fabric.pro/docs/operating/operator-console Inspect sessions, approvals, tenants, and persistent instances from the authenticated Node server. The Node server includes a compact operator console at `/admin`. It uses the same authentication, tenant binding, and `admin:read` permission checks as the admin APIs. ```sh FABRIC_HARNESS_API_TOKEN="$API_TOKEN" fh server ``` Open `http://localhost:4317/admin`, enter the API token, and connect. The token is kept in browser `sessionStorage` and sent as a bearer header; it is not written to the server-rendered page. The console provides paginated, searchable views for: - finite jobs and persistent agents; - sessions, runs, and active durable submissions; - built-in tools and pending or completed approvals; - persistence, queue, and worker health; - session cost metrics, policy decisions, and the audit trail. Operators with `approval:write` can approve or reject a pending request. Operators with `session:replay` can fork a session from any durable entry, while `session:abort` and `session:delete` control abort and cascade deletion. Buttons are hidden when the authenticated principal lacks the corresponding permission; the server independently authorizes every action. All collection endpoints use the same pagination shape: ```json { "items": [], "total": 0, "offset": 0, "limit": 50, "nextOffset": null } ``` Use `?offset=0&limit=50&q=running`. A tenant-bound principal only receives its own tenant. A principal allowed to operate several tenants can add `?tenant=acme`; the server validates that selection against the principal before reading data. Custom SSO deployments should use cookie authentication in `authenticate`, so the console can load without a separate bearer token. Assign operators `admin:read`, plus the action permissions they need. The APIs remain the source of truth; the console does not bypass RBAC or tenant isolation. --- # Private networking and egress Canonical: https://harness.fabric.pro/docs/operating/private-networking Enforce outbound policy at container, cluster, and cloud-provider boundaries, with proxy, DNS, custom CA, and mTLS support. Fabric applies URL, DNS-answer, and redirect policy before supported HTTP calls. Production deployments also need a boundary outside the agent process. That second layer prevents a custom tool, dependency, or direct socket from bypassing the application policy. ```mermaid flowchart LR A[Agent process] -->|policy-checked request| P[Internal egress proxy] A -. direct socket denied .-> X[Public or private service] P -->|domain and port allowlist| D[Databricks workspace APIs] P -->|private DNS| E[Private endpoints] subgraph Boundary[Container, cluster, or provider boundary] A P end classDef runtime fill:#e8f0fe,stroke:#2563eb,color:#172554 classDef control fill:#fff4d6,stroke:#d97706,color:#451a03 classDef service fill:#dcfce7,stroke:#16a34a,color:#052e16 classDef denied fill:#fee2e2,stroke:#dc2626,color:#450a0a class A runtime class P control class D,E service class X denied ``` ## Require an enforceable boundary `assertEnforceableNetworkPolicy()` rejects a network policy backed only by process-level checks. Call it during production startup after creating the sandbox: ```ts import { DockerSandboxEnv, assertEnforceableNetworkPolicy, type CapabilityPolicy, } from '@fabric-harness/sdk'; const policy: CapabilityPolicy = { network: { mode: 'allowlist', protocols: ['https:'], hosts: ['*.azuredatabricks.net', '*.databricks.com'], resolveDns: true, }, }; const sandbox = new DockerSandboxEnv({ workspacePath: process.cwd(), network: 'fabric-agent-internal', networkBoundary: { enforcement: 'container', reference: 'docker-network:fabric-agent-internal+egress-proxy', }, }); assertEnforceableNetworkPolicy(policy, sandbox, { deployment: 'production', }); ``` The `networkBoundary` value is an operator assertion, not a network provisioner. Create the Docker network with `internal: true`, connect the agent only to that network, and connect an independently configured proxy to both the internal and destination networks. The [`private-networking` example](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/private-networking) contains a runnable Compose topology that proves a proxied request succeeds while a direct request fails. ## Proxy, custom CA, and mTLS Use one transport factory for connectors that require enterprise TLS settings: ```ts import { readFile } from 'node:fs/promises'; import { createPrivateNetworkFetch } from '@fabric-harness/node'; const client = createPrivateNetworkFetch({ proxy: { url: process.env.HTTPS_PROXY!, authorization: `Bearer ${process.env.EGRESS_PROXY_TOKEN!}`, }, tls: { ca: await readFile('/var/run/fabric-secrets/private-ca.pem'), cert: await readFile('/var/run/fabric-secrets/client.crt'), key: await readFile('/var/run/fabric-secrets/client.key'), }, policy, }); try { const response = await client.fetch(`${process.env.DATABRICKS_HOST}/api/2.0/clusters/list`); if (!response.ok) throw new Error(`Workspace request failed: ${response.status}`); } finally { await client.close(); } ``` Keep proxy authorization and PEM material in a secret provider or mounted secret volume. Proxy URLs with embedded credentials are rejected so credentials cannot appear in URL logs. `rejectUnauthorized` defaults to `true`; do not disable certificate verification in production. ## Kubernetes and AKS `createKubernetesEgressNetworkPolicy()` emits deny-by-default egress with only three permitted paths: 1. selected cluster DNS pods on TCP/UDP 53; 2. a selected egress proxy on one TCP port; 3. explicitly listed private CIDRs and ports. ```ts import { createKubernetesEgressNetworkPolicy, kubernetesSandbox, } from '@fabric-harness/connectors/k8s'; const manifest = createKubernetesEgressNetworkPolicy({ namespace: 'fabric-agents', podSelector: { 'app.kubernetes.io/name': 'fabric-agent' }, egressProxy: { namespaceSelector: { 'kubernetes.io/metadata.name': 'networking' }, podSelector: { 'app.kubernetes.io/name': 'fabric-egress-proxy' }, port: 3128, }, privateCidrs: [{ cidr: '10.40.0.0/24', ports: [443] }], }); // Apply `manifest` with the cluster API before attaching the pod. const sandbox = kubernetesSandbox(pod, { networkPolicy: { namespace: manifest.metadata.namespace, name: manifest.metadata.name, }, }); ``` The generator rejects public CIDRs. Route public destinations through the proxy, where FQDN and certificate policy can be enforced. On AKS, combine this policy with Azure CNI, private clusters, Private Link, private DNS zones, workload identity, and an Azure Firewall or proxy route. Mount custom trust bundles and client certificates through the Secrets Store CSI Driver. ## Databricks private connectivity For Databricks Apps, keep the workspace identity supplied by the platform and configure connectivity at the workspace/account layer: - use workspace private connectivity for front-end and back-end API paths; - route SQL Warehouse, Model Serving, Vector Search, Lakebase, and Unity Catalog traffic through approved private endpoints where the workspace feature supports them; - use workspace DNS names in the Fabric allowlist and resolve them through the private DNS path; - exchange the App identity for short-lived Lakebase credentials instead of storing a database password; - store attachments in Unity Catalog Volumes and use service-principal or on-behalf-of identity, never embedded cloud storage keys. See [Databricks architecture](/docs/databricks/architecture), [enterprise Databricks controls](/docs/databricks/enterprise), and [Databricks Apps deployment](/docs/deployment/databricks-app) for the complete identity and data flow. ## Verification checklist - A direct request with proxy settings disabled cannot reach its destination. - An allowed proxied request succeeds; a non-allowlisted hostname is denied by the proxy. - Private DNS resolves only to the expected private ranges. - An untrusted server certificate fails, while the configured private CA succeeds. - The server requires and validates the expected client certificate. - `assertEnforceableNetworkPolicy()` passes with a named container, cluster, or provider boundary. - Logs, traces, errors, and build artifacts contain no proxy credentials, client keys, or workspace tokens. --- # MCP Canonical: https://harness.fabric.pro/docs/reference/mcp Model Context Protocol integration in Fabric Harness. Fabric Harness connects Model Context Protocol (MCP) servers to a session as normal Fabric tools. Remote Streamable HTTP, legacy SSE, and local stdio transports are supported. It can also expose Harness jobs, persistent agents, and governed tools as an authenticated MCP Streamable HTTP server. ## Expose Fabric through MCP ```ts import { startDevServer } from '@fabric-harness/node'; await startDevServer({ authenticate: companyAuthenticator, mcp: { enabled: true, exposeJobs: true, exposeAgents: true, tools: [lookupAccount], }, }); ``` Connect an MCP client to `https://agents.example.com/mcp`. Finite jobs appear as `job_` tools using their declared input schema. Persistent agents appear as `agent_` tools accepting `instanceId`, `message`, and optional `session`. Set `FABRIC_HARNESS_MCP_ENABLED=1` to enable the same surface in generated Node artifacts. The `/mcp` route requires `mcp:invoke`. The authenticated principal and tenant propagate into job runs and persistent submissions, including tool attribution and audit entries. MCP annotations are derived from Fabric tool effects, and tool errors are redacted before returning to the client. ## Remote MCP server Mount an MCP server's tools as session tools: ```ts import { connectMcpServer } from '@fabric-harness/sdk'; const github = await connectMcpServer('github', { url: process.env.GITHUB_MCP_URL!, headers: { authorization: `Bearer ${process.env.GITHUB_TOKEN}`, }, allowTools: ['get_*', 'list_*', 'search_*'], denyTools: ['*delete*'], }); await session.prompt('Look up project FAB-123', { tools: github.tools, }); await github.close(); ``` `connectMcpServer()` uses Streamable HTTP by default. Pass `transport: 'sse'` for a server that still exposes the legacy SSE transport. `allowTools` and `denyTools` accept exact names or `*` globs and filter the server catalog before any tool reaches the model. Provider credentials remain in transport or OAuth state and are not added to tool metadata or model context. The returned connection has `reconnect()` and `close()` lifecycle methods. Prompt and task abort signals propagate to active MCP calls, including the protocol cancellation notification. ## OAuth client credentials Use the machine-to-machine helper when the MCP authorization server supports the `client_credentials` grant. The MCP SDK discovers authorization metadata, obtains a token, and refreshes it when required. ```ts import { connectMcpServer, createMcpClientCredentialsAuth } from '@fabric-harness/sdk'; const authProvider = createMcpClientCredentialsAuth({ clientId: process.env.MCP_CLIENT_ID!, clientSecret: process.env.MCP_CLIENT_SECRET!, scope: 'tools.read', }); const connection = await connectMcpServer('enterprise-tools', { url: process.env.MCP_SERVER_URL!, authProvider, allowTools: ['catalog_*'], }); ``` ## OAuth authorization code Authorization code uses PKCE and application-owned state storage. In production, implement `loadState` and `saveState` with an encrypted server-side session or secret store. Never serialize this state into a prompt, agent memory, job payload, or client-side transcript. ```ts import { connectMcpServer, createMcpAuthorizationCodeAuth } from '@fabric-harness/sdk'; const authProvider = createMcpAuthorizationCodeAuth({ redirectUrl: 'https://agents.example.com/oauth/mcp/callback', clientName: 'Fabric Harness', clientId: process.env.MCP_CLIENT_ID!, clientSecret: process.env.MCP_CLIENT_SECRET, scopes: ['tools.read'], onAuthorizationUrl: (url) => redirectUser(url), loadState: () => encryptedSession.get('mcp-oauth'), saveState: (state) => encryptedSession.set('mcp-oauth', state), }); // On the callback request, pass the returned code once. Stored refresh tokens // are reused by subsequent connections through the same provider state. const connection = await connectMcpServer('user-tools', { url: process.env.MCP_SERVER_URL!, authProvider, authorizationCode: callbackUrl.searchParams.get('code') ?? undefined, }); ``` ## Local stdio server Use `createStdioMcpClient()` with `createMcpTools()` for a local MCP process: ```ts import { createMcpTools, createStdioMcpClient } from '@fabric-harness/sdk'; const client = createStdioMcpClient({ command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '/workspace'], }); const tools = await createMcpTools(client, { prefix: 'workspace', effect: 'read', source: 'filesystem', }); await session.prompt('Summarize the workspace', { tools }); await client.close(); ``` Tool names are prefixed and normalized before they enter the model toolset. Apply `toolPolicy` rules to the generated names when an MCP server exposes write or execute effects. ## Why MCP MCP is becoming a converging standard for tool/resource exchange across agent ecosystems. Wiring Fabric agents and MCP servers together means you can: - give Fabric agents access to a growing catalog of MCP-served tools, - use the same Fabric policy and approval controls for local and remote MCP tools, - switch between remote and stdio transports without changing the session prompt loop, - expose the same governed job and agent operations to IDEs, automation clients, and agent peers. --- # Channels Canonical: https://harness.fabric.pro/docs/ecosystem/channels Signed event ingress and outbound tools for persistent agents. Channels verify provider webhooks, normalize an event, derive a stable conversation key, and dispatch it to a persistent Fabric agent. Provider event IDs become dispatch IDs for deduplication, while tenant and actor identity flow into the audit trail. ```sh pnpm add @fabric-harness/channels @fabric-harness/sdk ``` | Channel | Stable instance key | Support | | --- | --- | --- | | [Discord](/docs/ecosystem/channels/discord) | Guild/channel/thread | First-party adapter | | [Facebook Messenger](/docs/ecosystem/channels/messenger) | Page/sender | First-party adapter | | [GitHub](/docs/ecosystem/channels/github) | Repository/issue or PR | First-party adapter | | [Google Chat](/docs/ecosystem/channels/google-chat) | Space/thread | First-party adapter | | [Intercom](/docs/ecosystem/channels/intercom) | Workspace/conversation | First-party adapter | | [Linear](/docs/ecosystem/channels/linear) | Organization/issue | First-party adapter | | [Microsoft Teams](/docs/ecosystem/channels/teams) | Tenant/conversation/thread | First-party adapter | | [Notion](/docs/ecosystem/channels/notion) | Workspace/page or database | First-party adapter | | [Resend](/docs/ecosystem/channels/resend) | Tenant/email | First-party adapter | | [Salesforce Marketing Cloud](/docs/ecosystem/channels/salesforce-marketing-cloud) | Business unit/contact or send | First-party adapter | | [Shopify](/docs/ecosystem/channels/shopify) | Shop/resource | First-party adapter | | [Slack](/docs/ecosystem/channels/slack) | Team/channel/thread | First-party adapter and example | | [Stripe](/docs/ecosystem/channels/stripe) | Connected account/customer or payment resource | First-party adapter | | [Telegram](/docs/ecosystem/channels/telegram) | Bot/chat/topic | First-party adapter | | [Twilio](/docs/ecosystem/channels/twilio) | Account/sender/recipient | First-party SMS and WhatsApp adapter | | [WhatsApp](/docs/ecosystem/channels/whatsapp) | Business account/phone/sender | First-party Meta Cloud API adapter | | [Zendesk](/docs/ecosystem/channels/zendesk) | Account/ticket | First-party adapter | | Generic webhook | Caller-provided conversation ID | Direct `fh add channel webhook` scaffold | First-party means Fabric owns the provider adapter, runs it through the shared Node and edge-safe channel contract, and publishes a dependency-aware `fh add channel ` recipe. ## Package and version policy Adapters are dependency-free `@fabric-harness/channels/` subpaths implemented with Fetch and Web Crypto. An adapter becomes a separate package only when a required provider SDK, runtime incompatibility, or independent release cadence makes that necessary. Import `channelCompatibility` from `@fabric-harness/channels/compatibility` for the machine-readable API matrix. Deprecated adapters receive at least 180 days of notice and are removed only in a major release. Channels dispatch to `.fabricharness/agents/`, not finite jobs. See [Persistent agents](/docs/building/persistent-agents) for the durable submission model. --- # Databases Canonical: https://harness.fabric.pro/docs/ecosystem/databases Durable session stores and governed database connectivity. Fabric uses databases for durable runtime state and exposes separate, policy-gated tools for agent data access. Do not give the model raw database credentials. | Database | Runtime use | Agent data access | | --- | --- | --- | | [libSQL](/docs/ecosystem/databases/libsql) | Unified local or remote bundle through `libsqlPersistence()`. | Scoped tool guide. | | [MongoDB](/docs/ecosystem/databases/mongodb) | Unified bundle through `mongodbPersistence()`. | First-party collection-bound find tools. | | [MySQL](/docs/ecosystem/databases/mysql) | Unified bundle through `mysqlPersistence()`. | First-party fixed-statement tools. | | [Postgres](/docs/ecosystem/databases/postgres) | Unified session, submission, stream, attachment, run/event, and cost persistence through `postgresPersistence()`. | First-party fixed-statement tools. | | [Redis](/docs/ecosystem/databases/redis) | Project-owned coordination/store implementation. | First-party namespaced get/set tools. | | [Supabase](/docs/ecosystem/databases/supabase) | Reuse `postgresPersistence()` with the direct Postgres connection. | RLS-aware table/RPC tool guide. | | [Turso](/docs/ecosystem/databases/turso) | Reuse `libsqlPersistence()` with a remote URL/token. | Parameterized libSQL tool guide. | | [Valkey](/docs/ecosystem/databases/valkey) | Reuse `redisPersistence()` with a Redis-protocol client. | Namespaced command tool guide. | | [Databricks Lakebase](/docs/ecosystem/databases/lakebase) | Sessions, submissions, conversation streams, and attachments for Databricks Apps. | Use governed Databricks SQL and Unity Catalog tools separately. | | [SQLite](/docs/ecosystem/databases/sqlite) | Local durable sessions through Node configuration. | First-party fixed-statement tools. | See [Session stores](/docs/reference/session-stores) for the complete persistence contract. Install governed data tools with `pnpm add @fabric-harness/databases` or scaffold a complete provider module with `fh add database postgres`, `mysql`, `mongodb`, `redis`, or `sqlite`. Postgres and Lakebase, MySQL, MongoDB, libSQL/Turso, Redis/Valkey, SQLite, and Supabase/Postgres provide durable Fabric stores. Data tools and session persistence remain separate so model-facing access never receives runtime-store credentials implicitly. --- # Agent Anatomy Canonical: https://harness.fabric.pro/docs/building/anatomy The metadata-first shape of a Fabric Harness agent — default and strict variants of the same call. import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Fabric Harness finite jobs are metadata-first. The recommended default export is `defineJob({...})`, which provides lazy `prompt`, `skill`, `task`, `shell`, and `session` helpers. The lower-level `agent({...})` builder remains available when explicit `init()` control is clearer. Persistent agents use `defineAgent()` under `.fabricharness/agents/`. - **Default** — `import { agent } from '@fabric-harness/sdk'`. Injects headless defaults (`runtime: 'stateless'`, `sandbox: 'virtual'`, `loopRuntime: pi-agent-core`, `compaction: { enabled: true }`) on every `init()` call. The fast path for prototypes, webhooks, edge agents. - **Strict** — `import { agent } from '@fabric-harness/sdk/strict'`. Same call shape, **no defaults injected**. Required for Temporal-backed durability (replay determinism) and recommended for compliance/audit workloads. Both produce the same `AgentDefinition` and run identically through `fh run`, `fh build`, `fh describe`, and any deploy target. ## Recommended job definition ```ts import { defineJob, schema } from '@fabric-harness/sdk'; export default defineJob({ name: 'ask', input: schema.object({ question: schema.string() }), output: schema.string(), async run({ input, prompt }) { return prompt(input.question); }, }); ``` ## Invoke another finite job Use the job context's `invoke()` for child work. The configured runtime admits a new run without an HTTP round trip and propagates the parent run, tenant, and actor. Cycles and nesting beyond 16 jobs are rejected before admission. ```ts title=".fabricharness/jobs/account-review.ts" import { defineJob, schema } from '@fabric-harness/sdk'; export default defineJob({ input: schema.object({ accountId: schema.string() }), run: async ({ input, invoke }) => { const receipt = await invoke({ job: 'collect-account-evidence', input: { accountId: input.accountId }, idempotencyKey: `evidence:${input.accountId}`, }); return { evidenceRunId: receipt.runId }; }, }); ``` When a definition has a declared or loader-registered name, `invoke(definition, { input })` is also available. The top-level `invoke()` export remains available in application routes, channels, and schedules. Use the named form across module boundaries to avoid importing executable job modules. ## Middleware and run identity Job middleware wraps `run` in declaration order. It receives the same typed context and is suitable for tracing, shared authorization, metrics, and transaction boundaries. `context.run` contains the stable run ID, job name, parent chain, tenant, and actor when the job was admitted through a server; it is absent for a direct handler call in a unit test. ```ts import { defineJob, schema } from '@fabric-harness/sdk'; export default defineJob({ name: 'account-review', input: schema.object({ accountId: schema.string() }), middleware: [ async ({ run }, next) => { console.log({ event: 'started', runId: run?.runId }); const output = await next(); console.log({ event: 'completed', runId: run?.runId }); return output; }, ], async run({ input, prompt }) { return prompt(`Review account ${input.accountId}`); }, }); ``` Each middleware may call `next()` once. It may also short-circuit by returning a typed output. The runtime and default session are initialized on the first helper call. Add `init: { sandbox: 'local' }` to set initialization options, or call `session('review')` when you need a named session. ## Lower-level job definition ```ts import { agent, schema } from '@fabric-harness/sdk'; export default agent({ name: 'ask', input: schema.object({ question: schema.string() }), output: schema.string(), triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); return session.prompt(input.question); }, }); ``` `init()` defaults are injected automatically. Override any of them by passing a value: `init({ runtime: 'inline', sandbox: 'docker' })`. ```ts import { agent, schema } from '@fabric-harness/sdk/strict'; export default agent({ name: 'ask', input: schema.object({ question: schema.string() }), output: schema.string(), triggers: { webhook: true }, run: async ({ init, input }) => { const fabric = await init({ runtime: 'temporal', sandbox: 'local', compaction: { enabled: false }, }); const session = await fabric.session(); return session.prompt(input.question); }, }); ``` Every option is declared in source. Nothing implicit. Required for Temporal — auto-compaction would break replay determinism. The `agent` call shape is identical across both imports. Plain default-exported functions are intentionally rejected because a definition builder keeps jobs discoverable and gives the CLI typed metadata. Top-level finite-definition `instructions`, `tools`, `policy`, `costBudget`, and approval timeout are applied to `init()`. A call-provided role overrides definition instructions; definition policies and budgets remain security floors, while call policy can add denials and approval requirements. ## The `FabricContext` When the CLI invokes an agent it provides: ```ts interface JobContext { payload: TInput; input: TInput; init(options?: AgentInit): Promise; run?: JobInvocationContext; session(id?: string): Promise; prompt(text: string): Promise; skill(name: string): Promise; task(text: string): Promise; shell(command: string): Promise; invoke(request: NamedJobInvocation): Promise; } ``` Use `input` for typed, schema-validated values inside `run`: ```ts run: async ({ init, input }) => { const fabric = await init(); const session = await fabric.session(); return session.prompt(input.question); } ``` ## `init()` options Agents from either entrypoint call the same `init()` to construct the runtime: ```ts const fabricAgent = await init({ id: 'agent-1', model: 'openai/gpt-5.5', role: 'engineer', // Markdown role file under .fabricharness/roles/ sandbox: 'local', // 'virtual' | 'empty' | 'local' | 'docker' | 'cloudflare' | factory autonomy: { mode: 'background', onMissingInput: 'assume', onApprovalUnavailable: 'fail', onCredentialMissing: 'fail', }, }); ``` When using the bare `@fabric-harness/sdk` import you typically omit `runtime`, `sandbox`, and `loopRuntime` — those defaults are injected. Override anything you like; defaults fill the gaps you don't set. > ⚠️ **Temporal users:** if you set `runtime: 'temporal'` from the bare import, the SDK emits a one-time `console.warn`. Auto-compaction is non-deterministic across Temporal replay. Either switch to `@fabric-harness/sdk/strict` or pass `compaction: { enabled: false }` explicitly. ## Triggers Declare triggers inside `agent({...})`. The Node and Cloudflare server targets respect: ```ts export default agent({ name: 'triage', triggers: { webhook: true, // POST /jobs/triage schedule: '*/15 * * * *', // Node scheduler or Cloudflare Cron Trigger // cli: true, // CLI-only, default true }, async run(ctx) { return ctx.input; }, }); ``` ## Where to go next - [SDK entrypoints, runtimes, and targets](/docs/reference/sdk-entrypoints-runtimes-targets) — when to swap to `/strict`. - [Enterprise controls](/docs/building/enterprise-controls) — definition-level policy, tools, budgets, and approvals. - [Sessions and prompts](/docs/building/sessions-prompts) — the core call surface. - [Skills](/docs/building/skills) and [Roles](/docs/building/roles). - [Tools](/docs/building/tools) and [Commands](/docs/building/commands). - [Sandboxes](/docs/building/sandboxes). --- # Approvals Canonical: https://harness.fabric.pro/docs/building/approvals Pause an agent until a human approves a risky action. Approvals turn an autonomous agent into one that asks for confirmation at risky moments. The framework persists the request, suspends the relevant code path, and resumes when a human (or another system) resolves it. ## Declarative routing — `approvalRules` Most users want approvals tied to *specific tools*, not scattered through agent code. Declare the rules once in policy and the loop pauses for approval before dispatch: ```ts const fabric = await init({ policy: { toolPolicy: { approvalRules: [ { pattern: 'submit_*', audience: 'reviewer', reason: 'Review before submission of ${name}' }, { pattern: 'delete_*', audience: 'project-admin', ttlSeconds: 3600 }, { pattern: 'finalize_*', audience: 'compliance-team' }, ], }, }, }); ``` `audience` is an opaque string id. fabric-harness never decides who an audience maps to — your host application's identity layer (UI, SSO, RBAC) does that mapping. Rules also work on `commandPolicy.approvalRules` for `bash` invocations. When the agent calls a matching tool, fabric-harness emits `approval_requested` with the audience id, the templated reason, and the TTL. The host UI renders the request to the right humans; on `approval_granted` the loop continues; on denial or TTL it throws. This is sugar over the imperative `session.approval.request()` API — which stays available as the escape hatch for ad-hoc cases. ## Webhook subscriptions Agents that should wake on inbound events (event bus, queue, external SaaS webhook) use `defineWebhookSubscription`: ```ts import { agent, defineWebhookSubscription } from '@fabric-harness/sdk'; export default agent({ name: 'data-validator', triggers: { webhook: true }, subscriptions: [ defineWebhookSubscription<{ recordId: string }>({ id: 'on-record-created', events: ['record.created'], handler: async ({ payload, idempotencyKey, headers }) => { // ...invoke whatever your host application exposes. }, }), ], async run() { /* ... */ }, }); ``` `fh server` exposes each subscription at `POST /agents//subscriptions/`. Set `FABRIC_HARNESS_WEBHOOK_SECRET` to require an `X-Fabric-Signature: sha256=` header on every delivery. The server dedupes by `Idempotency-Key` for at-least-once event buses, and emits a `webhook_received` event into the session log for audit. `fh subscriptions [agent]` lists registered subscriptions across the workspace. ## Request an approval `session.approval.request()` returns `true` when the approver allows the action. It throws a `FabricError` (`APPROVAL_DENIED` or `APPROVAL_REQUIRED`) on denial or timeout — wrap in try/catch when you want to handle rejection gracefully. ```ts import { FabricError } from '@fabric-harness/sdk'; try { await session.approval.request({ reason: 'About to push changes to GitHub', subject: 'git push origin main', risk: 'high', timeoutMs: 24 * 60 * 60 * 1000, // 24h }); await session.shell('git push origin main'); } catch (error) { if (error instanceof FabricError && error.code === 'APPROVAL_DENIED') { throw new Error(`Push blocked: ${error.message}`); } throw error; } ``` The available fields are: | Field | Type | Description | |---|---|---| | `reason` | `string` | Human-readable reason shown to the approver. | | `subject` | `string?` | Short subject line for UIs. Defaults to `'custom'`. | | `risk` | `'low' \| 'medium' \| 'high'?` | Drives escalation policy. | | `timeoutMs` | `number?` | Override the session's default approval timeout. | | `onApproval` | `ApprovalCallback?` | Per-call approval handler. Falls back to session/agent `onApproval`. | ## Resolve from the CLI ```sh fh approvals --pending fh approve --actor preetham # or fh reject --actor preetham --reason "Wrong branch" ``` ## Notify approvers Attach `approvalNotificationHandler()` to the agent's `onEvent` callback. It receives requested, escalated, and resolved events, retries transient failures, deduplicates deliveries, and records delivery outcomes through the audit hook. Notification failures never resolve the approval. ```ts import { approvalNotificationHandler, agent, slackApprovalNotifier, } from '@fabric-harness/sdk'; import { redisApprovalNotificationStore } from '@fabric-harness/node'; const notifyApproval = approvalNotificationHandler({ notifier: slackApprovalNotifier({ webhookUrl: process.env.SLACK_APPROVAL_WEBHOOK_URL!, }), baseUrl: 'https://agents.example.com', store: redisApprovalNotificationStore({ client: redis, namespace: 'production', }), retries: 4, deadLetter: async (record) => { await approvalDeadLetters.insert(record); }, audit: async (record) => { await auditLog.append('approval_notification', record); }, onError: (error, notification) => { logger.error({ error, notificationId: notification.id }); }, }); export default agent({ name: 'release-agent', onEvent: notifyApproval, async run({ prompt }) { return prompt('Prepare the approved release.'); }, }); ``` Use `webhookApprovalNotifier()` for an internal workflow service, PagerDuty bridge, or custom notification worker. Slack and generic webhook destinations receive a bounded public payload: raw tool input, environment values, actor credentials, and destination secrets are excluded. Deep links contain only the session, approval, and tenant identifiers; `/admin` still enforces authentication, tenant isolation, and approval permissions when opened. `inMemoryApprovalNotificationStore()` is suitable for local development. Use `redisApprovalNotificationStore()` across replicas or implement `ApprovalNotificationDeliveryStore` with an atomic claim in the system that owns your notification outbox. A dead-letter callback is strongly recommended in production. ## Durable waits On the Temporal worker target, the approval wait runs as a dedicated workflow and can wait without holding a process. The workflow records its ID on the approval request so `fh approve` and `fh reject` signal the owning workflow. The first denial wins; approvals are deduplicated by actor and resume only after `policy.approvals.requiredApprovals` is reached. An escalation deadline emits `approval_escalated` with the configured notify audience and risk, while the overall timeout remains authoritative. Worker restarts do not lose the timer, votes, or pending request. On the inline runtime, the wait uses the configured session store or callback. To make agents safe on either runtime, declare autonomy fallbacks: ```ts await init({ autonomy: { onApprovalUnavailable: 'fail', // or 'assume-rejected' | 'assume-approved' }, }); ``` ## Logged identities Approvals record both the agent identity and the human actor. Combined with the two-identity actor schema (Entra Agent ID + on-behalf-of user), the audit trail answers "which agent acted, on whose behalf, who approved?" ## See also - [Policies and approvals](/docs/reference/policies-approvals) - [`fh approvals`](/docs/cli/approvals) --- # Artifacts Canonical: https://harness.fabric.pro/docs/building/artifacts Publish session-bound files for downstream consumers. An **artifact** is a session-bound file. Agents publish artifacts as the durable, reviewable output of their work — Markdown reports, JSON results, CSVs, images. ## Publish an artifact ```ts await session.artifacts.publish('/workspace/report.md', { name: 'report.md', contentType: 'text/markdown', }); ``` You can also pass an in-memory buffer: ```ts await session.artifacts.publish({ name: 'summary.json', contentType: 'application/json', data: JSON.stringify({ ok: true }), }); ``` ## Read from the CLI ```sh fh artifacts fh artifact get report.md --out ./reports/report.md ``` ## What gets persisted The session store records artifact metadata (id, name, content type, byte size, created time) alongside the session entry that produced it. The bytes are stored separately: - **File store** (default): files under `.fabricharness/sessions//artifacts/`. - **SQLite store**: BLOB column. - **Postgres store**: BYTEA column or external blob URL. - **Cloud stores** (designed): Azure Blob / ADLS, S3, R2. ## Patterns - **Reports.** A triage agent publishes `triage-report.md` for reviewers. - **Generated code.** A migration agent publishes the proposed diff as an artifact and asks for approval before applying it. - **Datasets.** A data agent publishes profiled CSVs alongside the prompt that produced them. See also: [`fh artifacts`](/docs/cli/artifacts), [`fh artifact get`](/docs/cli/artifacts). --- # Channels Canonical: https://harness.fabric.pro/docs/building/channels Turn platform webhooks (Slack, GitHub, …) into agent dispatches. Channels are how agents get **triggered** by the outside world. A webhook hits `/channels/:name/*`, the handler verifies the signature, normalizes the platform event, and **dispatches** to a persistent agent keyed by a stable conversation id. Outbound actions are tools bound at agent init. The core seam lives in `@fabric-harness/sdk`; 17 vendor adapters live in `@fabric-harness/channels` behind subpath exports, so platform SDK code stays out of core. Handlers are written against the Web `Request`/`Response` API and `crypto.subtle`, so the same channel runs on Node and Cloudflare. ## Why channels - **Stateless.** A channel is a route container plus a conversation-id (de)serializer. Session continuity falls out of the key — the same Slack thread → same key → same session. - **Exactly-once.** The platform event id (Slack `event_id`, GitHub `X-GitHub-Delivery`) becomes the `dispatchId`, so a webhook redelivery collapses to a single agent turn via the persistent-run idempotency marker. - **Identity propagation.** The platform user becomes the dispatch `actor` and the team/org becomes the `tenantId` — flowing into the audit trail and into on-behalf-of governance (e.g. Databricks UC). - **Policy-gated outbound.** Outbound actions are `defineTool` tools (`effect: 'write'`), so the agent's `CapabilityPolicy` can gate "can this agent post to Slack". ## Slack A channel lives in `.fabricharness/channels/.ts` and exports a `channel`: ```ts title=".fabricharness/channels/slack.ts" import { createSlackChannel } from '@fabric-harness/channels/slack'; export const channel = createSlackChannel({ signingSecret: process.env.SLACK_SIGNING_SECRET!, agent: 'assistant', // dispatch app mentions / threaded messages here }); ``` The dev server mounts it at `POST /channels/slack/events`. Point your Slack app's **Event Subscriptions → Request URL** there and subscribe to `app_mention`. The handler verifies the `v0` HMAC signature, answers the URL-verification challenge, and dispatches the event keyed by the thread. Bind the reply tool to the thread at agent init — the instance id *is* the thread key: ```ts title=".fabricharness/agents/assistant.ts" import { createAgent } from '@fabric-harness/sdk'; import { parseSlackConversationKey, replyInSlackThread } from '@fabric-harness/channels/slack'; export default createAgent(({ id }) => { const thread = parseSlackConversationKey(id); return { model: 'anthropic/claude-haiku-4-5', tools: [replyInSlackThread(thread, { botToken: process.env.SLACK_BOT_TOKEN! })], }; }); ``` ## GitHub Same shape, different signature scheme (`X-Hub-Signature-256`) and key (`owner/repo//`): ```ts title=".fabricharness/channels/github.ts" import { createGitHubChannel } from '@fabric-harness/channels/github'; export const channel = createGitHubChannel({ secret: process.env.GITHUB_WEBHOOK_SECRET!, agent: 'triage', }); ``` Mounted at `POST /channels/github/webhook`. It acknowledges `ping`, dispatches issue / PR / comment events (with `X-GitHub-Delivery` as the dedupe key, the owner as tenant, the sender as actor), and ignores bot senders to avoid loops. Outbound: `commentOnGitHubIssue(ref, { token })`. ## Other first-party channels Scaffold the provider module, environment template, dependency set, and starter test: ```sh fh add channel discord fh add channel teams fh add channel telegram fh add channel twilio fh add channel whatsapp fh add channel google-chat fh add channel linear fh add channel stripe fh add channel zendesk ``` Each first-party adapter verifies the provider's native request authentication, assigns the provider event ID as `dedupeKey`, derives a stable conversation key, and propagates tenant/actor identity. Provider setup and outbound tool examples are in the [channel catalog](/docs/ecosystem/channels). Microsoft Teams includes Bot Connector key discovery and validates issuer, App ID audience, token lifetime, service URL, and channel endorsement without requiring an application JWT library. The subpath exports include typed webhook payloads and conversation refs for custom routing. The built-in normalization covers the event families an agent commonly needs: | Provider | Normalized events | | --- | --- | | Slack | mentions, messages, edited messages | | GitHub | opened, edited, reopened, synchronized, and issue/PR comments | | Discord | commands and component callbacks | | Teams | messages, message updates, and deletes | | Telegram | messages, edited messages, channel posts, and callback queries | | Twilio | SMS/WhatsApp messages and delivery statuses | | WhatsApp | text messages and delivery/read statuses | | Google Chat | messages and interactions | | Linear | issue and project resource changes | | Notion | workspace entity changes | | Stripe | account and financial resource events | | Zendesk | ticket events | | Intercom | conversation and ticket notifications | | Shopify | versioned commerce webhooks | | Messenger | messages and postbacks | | Resend | inbound and delivery email events | | Salesforce Marketing Cloud | signed ENS event batches | Delivery-status dedupe keys include both message ID and status, so a `sent` event cannot suppress a later `delivered` or `read` event. ## Authoring your own channel A channel is `defineChannel({ routes, conversationKey, parseConversationKey })`. The SDK provides the building blocks: `verifyHmacSha256` (constant-time), `hexToBytes`, `conversationKey` / `parseConversationKey` (url-safe), and `readJsonBody` (raw bytes for HMAC **and** parsed JSON from a single read). Inside a route handler, call `ctx.dispatch(agent, { instanceId, input, dedupeKey, tenantId, actor })`. ## See also - Example: [`examples/with-slack-channel`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-slack-channel) — Slack mention → agent → in-thread reply, end to end. - Example: [`examples/with-channel-adapters`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-channel-adapters) — all 17 verified ingress adapters and their governed outbound tools in one runnable workspace. - [Persistent agents](/docs/building/persistent-agents) — channels dispatch to `createAgent(...)` instances. - [Triggers](/docs/reference/triggers) — gating which agents accept inbound events. --- # Commands and Capabilities Canonical: https://harness.fabric.pro/docs/building/commands Scope shell commands and secrets to a session. A **command** in Fabric Harness is a shell-level capability you explicitly grant to a session — not a generic "run anything" door. Use `defineCommand` from `@fabric-harness/node` to declare one. ## Declaring commands ```ts import { defineCommand } from '@fabric-harness/node'; const npm = defineCommand('npm'); const git = defineCommand('git'); const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN, }, }); ``` A `defineCommand` declaration captures: - the binary name, - environment variables to inject, - working directory defaults, - timeouts and stdin handling. ## Granting commands per call ```ts await session.prompt('Fix the failing tests', { commands: [npm, git, gh], }); ``` The model can only run shell commands whose binary matches one of the declared commands. Anything else fails with a capability error. ## Secrets Use `secret()` to mark a value as a credential reference. The runtime resolves it at exec time and never echoes it into model context. Exported from `@fabric-harness/sdk`. ```ts import { defineCommand, secret } from '@fabric-harness/sdk'; const gh = defineCommand('gh', { env: { GH_TOKEN: secret('GH_TOKEN') }, }); ``` `secret()` is intentionally a token, not the value: it can be passed around without leaking its content into logs, traces, or the LLM context. ## Capability policy Use `policy` to enforce filesystem, command, tool, network, timeout, and approval rules. Policies can be set on `init()`, a session, or an individual prompt; narrower call-level policy is combined with definition-level controls. ```ts await session.prompt('Fix the tests', { policy: { filesystem: { read: ['/workspace/**'], write: ['/workspace/src/**', '/workspace/tests/**'], }, commandPolicy: { allow: ['npm test', 'git diff', 'git status'], requireApproval: ['git push', 'npm publish', 'terraform apply'], }, network: { mode: 'allowlist', hosts: ['api.github.com'], }, maxCommandTimeoutMs: 120_000, }, }); ``` The runtime applies policy at both the tool layer and the sandbox boundary. See [Policies and approvals](/docs/reference/policies-approvals) for deny rules, approval routing, and composition behavior. --- # Connector catalog Canonical: https://harness.fabric.pro/docs/building/connector-catalog Sandbox, MCP, knowledge-base, data, Azure, and Databricks connector options. Fabric Harness connectors are intentionally modular. The core SDK owns the stable agent/session/tool/sandbox contracts; provider packages and project-local adapters own provider SDKs, credentials, lifecycle, and live compatibility. There are three connector forms: | Form | Use when | Example | |---|---|---| | Package helper | Fabric already ships a dependency-free helper or REST client. | `daytonaSandbox`, `databricksSqlTool`, `foundryAgentTool` | | Recipe via `fh add` | Provider SDK shape or project conventions need local code. | `fh add daytona --print` | | Direct SDK primitive | No connector file is needed. | `fumadocsSource`, `connectMcpServer` | Discover scaffold and guide recipes from one catalog: ```sh fh add fh add --json ``` The JSON form returns stable IDs such as `scaffold:channel:slack` and `guide:mcp:github-mcp`, plus the exact command for each entry. Catalog entries also include declared dependencies. Direct scaffolds install missing dependencies with the detected package manager; use `--no-install` for manifest-only changes. Guide recipes remain side-effect free unless `--install-deps` is supplied. ## What `fh add` installs Named recipes either add a maintained package adapter or generate a project-local connector that uses the provider SDK directly. The command preview lists every dependency and file before installation: ```sh fh add slack --print fh add notion-channel --print fh add --json ``` Use the [ecosystem catalog](/docs/ecosystem) to open the provider-specific setup, identity mapping, webhook verification, policy, and validation instructions. ## Quickstart for any provider If no pre-built adapter exists, use `fh add --category ` and pipe to your coding agent: ```sh fh add https://e2b.dev --category sandbox | claude fh add https://api.notion.com --category data | cursor-agent fh add https://docs.linear.app --category mcp | codex ``` The CLI emits the canonical category spec (`sandbox.md`, `mcp.md`, or `data.md` shipped with `@fabric-harness/sdk`) along with a "build me a connector for this URL" header. The agent reads the provider's docs, follows the spec literally, and writes a single TypeScript file at `./connectors/.ts`. No PR to fabric-harness needed — the file lives in your project. When fabric-harness ships a first-party recipe for the provider, prefer the named slug: ```sh fh add daytona | claude fh add linear-mcp | claude ``` ### Auto-PR mode `fh add daytona --pr` opens a draft GitHub PR with a stub connector file (committed to a `connector/` branch) and the canonical spec embedded in the PR body. You (or your coding agent) push commits that flesh out the stub. Requires the `gh` CLI and a configured remote. ```sh fh add daytona --pr fh add https://your-provider.example.com --category sandbox --pr --pr-branch=connector/your-provider ``` ### Validating connectors `fh doctor --connectors` walks `connectors/` and `.fabricharness/connectors/` and flags files that are missing exports, lack the `@fabric-harness/*` import, or are otherwise malformed: ```sh fh doctor --connectors # ✓ connector:daytona.ts: ./connectors/daytona.ts # ✗ connector:broken.ts: missing @fabric-harness/* import # ✓ connectors: 1/2 healthy ``` ## Install packages Core connector helpers: ```sh npm install @fabric-harness/sdk @fabric-harness/connectors ``` Cloud-specific packages: ```sh npm install @fabric-harness/azure npm install @fabric-harness/databricks ``` Provider SDKs stay in your app, not in `@fabric-harness/sdk`: ```sh npm install @daytona/sdk npm install @e2b/code-interpreter ``` ## Sandbox connectors Sandbox connectors adapt remote execution providers to Fabric's `SandboxEnv` contract: file operations, shell execution, cwd scoping, cleanup, and optional snapshots. | Connector | Package helper | Recipe | Validation | Notes | |---|---|---|---|---| | Daytona | `daytonaSandbox()` / `daytonaSandboxFactory()` from `@fabric-harness/connectors` | `fh add daytona` | `FABRIC_DAYTONA_TEST=1` | Wraps an initialized Daytona sandbox. | | E2B | `e2bSandbox()` | `fh add e2b` | `FABRIC_E2B_TEST=1` | Maps E2B command and file APIs. | | Modal | `modalSdkSandbox()` | `fh add modal` | `FABRIC_MODAL_TEST=1` | Native Modal TypeScript SDK process/filesystem adapter. | | Vercel Sandbox | `vercelSandbox()` | `fh add vercel` | `FABRIC_VERCEL_TEST=1` | Native SDK command/filesystem adapter with reconnect and streamed output. | | Custom provider | `remoteSandbox()` / `remoteSandboxEnv()` | `fh add --category sandbox --print` | `validateSandboxAdapter(env)` | Keep provider SDK and credentials in project code. | ### Daytona example ```ts import { Daytona } from '@daytona/sdk'; import { daytonaSandbox } from '@fabric-harness/connectors'; const client = new Daytona({ apiKey: process.env.DAYTONA_API_KEY }); const remote = await client.create(); const fabric = await init({ sandbox: daytonaSandbox(remote, { cleanup: true }), }); ``` ### Validate any sandbox adapter ```ts import { validateSandboxAdapter } from '@fabric-harness/connectors'; await validateSandboxAdapter(env); ``` The validator runs a smoke contract: `mkdir`, `writeFile`, `readFile`, `exec`, and `rm`. See [Sandbox connectors](/docs/building/sandbox-connectors) for the full adapter guide. ## MCP connectors MCP connectors expose remote tools to agents. Use `connectMcpServer(name, options)` from `@fabric-harness/sdk` (or `/strict` if you prefer). | Connector | Recipe | Pattern | |---|---|---| | GitHub MCP | `fh add github-mcp --print` | Hosted MCP with bearer token. | | Mintlify MCP | `fh add mintlify-mcp --print` | Streamable HTTP MCP endpoint for a docs site when available. | | Linear MCP | `fh add linear-mcp --print` | Hosted MCP with API token. | | Slack MCP | `fh add slack-mcp --print` | Hosted or self-hosted bridge. | | Custom MCP | `fh add --category mcp --print` | Reads provider docs and generates a connector. | Example: ```ts import { connectMcpServer } from '@fabric-harness/sdk'; const github = await connectMcpServer('github', { url: 'https://mcp.github.com/mcp', transport: 'streamable-http', headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }, }); try { const session = await (await init({ tools: github.tools })).session(); await session.prompt('Find open issues tagged bug.'); } finally { await github.close(); } ``` ## Knowledge-base connectors Knowledge-base connectors mount docs/content into a sandbox as files. Agents then use `read`, `grep`, and `glob` instead of a retrieval pipeline. | Source | Helper | Recipe | Notes | |---|---|---|---| | Local Markdown/MDX | `localDirectorySource()` | none | General local docs. | | Fumadocs | `fumadocsSource()` | `fh add fumadocs --print` | Strips frontmatter by default. | | Mintlify local checkout | `mintlifySource()` | none | Includes `docs.json` nav. | | HTTP docs | `httpFilesystemSource()` | none | Fetch URLs into files. | | Cloudflare R2 | `r2FilesystemSource()` from `@fabric-harness/sdk/cloudflare` | docs example | Worker/R2 deployments. | | S3-compatible | `s3FilesystemSource()` from `@fabric-harness/connectors` | none | Structural object-store client. | | Azure Blob | `azureBlobFilesystemSource()` from `@fabric-harness/connectors` | none | Structural blob client. | Example: ```ts import { agent, fumadocsSource, withFilesystemSources } from '@fabric-harness/sdk'; export default agent<{ question: string }>({ run: async ({ init, input }) => { const sandbox = withFilesystemSources('virtual', [{ mountAt: '/workspace/docs', source: fumadocsSource('./apps/docs/content/docs'), }]); const session = await (await init({ sandbox })).session(); return await session.prompt(`Answer using /workspace/docs: ${input.question}`); }, }); ``` ## Data connectors Data connectors should expose narrow tools, not raw credentials or arbitrary network access. | Connector | Package/helper | Recipe | Notes | |---|---|---|---| | Postgres | project-local `ToolDef`/command | `fh add postgres --print` | Prefer read-only credentials and row/time limits. | | Notion | hosted MCP or REST command | `fh add notion --print` | Hosted MCP when available; REST adapter otherwise. | | Databricks SQL | `databricksSqlTool()` | see [Databricks](/docs/deployment/databricks) | SQL Warehouse statements. | | Databricks Jobs | `databricksRunJobTool()` | see [Databricks](/docs/deployment/databricks) | Trigger existing jobs. | | Unity Catalog | `unityCatalogTablesTool()` | see [Databricks](/docs/deployment/databricks) | Read catalog/table metadata. | | MLflow | `databricksMlflowLogMetricTool()` / `databricksMlflowLogParamTool()` | see [Databricks](/docs/deployment/databricks) | Write run telemetry. | ## Azure and Foundry connectors Azure helpers live in `@fabric-harness/azure`: | Helper | Purpose | |---|---| | `AzureOpenAIModelProvider` | Azure OpenAI model calls. | | `createAzureBlobArtifactStore()` | Blob-backed artifacts. | | `createAzureKeyVaultSecretResolver()` | Runtime secret resolution. | | `FoundryAgentServiceClient` | Foundry Agent Service REST client. | | `foundryAgentTool()` | Invoke a Foundry agent from a Fabric agent. | | `foundryAgentLifecycleTools()` | Create/update/delete Foundry agents as gated tools. | | `azureContainerAppsJobTool()` | Start existing Container Apps Jobs. | | `azureAksRunCommandTool()` | AKS Run Command. | | `azureContainerInstanceExecTool()` | ACI exec-session creation. | See [Azure](/docs/deployment/azure) and [Foundry Hosted Agents](/docs/deployment/foundry-hosted-agent). ## `fh add` List recipes: ```sh fh add ``` Print one: ```sh fh add daytona --print fh add github-mcp --print fh add postgres --print ``` Generate from provider docs: ```sh fh add https://example.com/provider/docs --category sandbox --print ``` Categories: | Category | Output shape | |---|---| | `sandbox` | `SandboxEnv` / `SandboxFactory` | | `mcp` | `connectMcpServer()` wrapper | | `kb` | `FilesystemSource` | | `data` | `ToolDef`, `Command`, or MCP wrapper | ## Live tests Connector live tests are opt-in. See [Live tests](/docs/reference/live-tests) for the full matrix. ```sh FABRIC_DAYTONA_TEST=1 DAYTONA_API_KEY=... pnpm --filter @fabric-harness/connectors test FABRIC_AZURE_FOUNDRY_TEST=1 AZURE_FOUNDRY_PROJECT_ENDPOINT=... AZURE_TOKEN=... pnpm --filter @fabric-harness/azure test FABRIC_DATABRICKS_TEST=1 DATABRICKS_HOST=... DATABRICKS_TOKEN=... pnpm --filter @fabric-harness/databricks test ``` ## Security checklist - Keep provider credentials in env, managed identity, Key Vault, or secret manager. - Never put tokens in prompts, payloads, session history, artifacts, or build manifests. - Prefer read-only credentials for data tools. - Treat sandbox execution, cloud control-plane actions, and data writes as `execute`/`write` effects. - Gate destructive actions with Fabric policy approvals. - Add unit tests with fake provider objects and live tests behind env gates. --- # Enterprise Controls Canonical: https://harness.fabric.pro/docs/building/enterprise-controls Apply tools, policy, approvals, budgets, and instructions at the job definition boundary. Finite jobs can declare their runtime controls once at the definition boundary. The wrapper applies them whenever `run()` calls `init()`, including through CLI, development server, and built artifacts. ## Complete governed job Create `.fabricharness/jobs/governed-report.ts`: ```ts import { defineTool, job, schema } from '@fabric-harness/sdk'; const lookupAccount = defineTool({ name: 'lookup_account', description: 'Read an account summary.', inputSchema: { type: 'object', additionalProperties: false, properties: { accountId: { type: 'string' } }, required: ['accountId'], }, execute: async ({ accountId }: { accountId: string }) => ({ accountId, status: 'active', }), }); export default job({ name: 'governed-report', description: 'Create a read-only account report.', instructions: 'Use only approved account data. Never infer missing values.', input: schema.object({ accountId: schema.string() }), output: schema.string(), triggers: { webhook: true, manual: true }, tools: [lookupAccount], policy: { toolPolicy: { allow: ['lookup_account'], deny: ['write', 'edit', 'bash'], requireApproval: ['export_*'], }, network: { mode: 'none' }, approvals: { requiredApprovals: 2, risk: 'high', defaultTimeoutMs: 5 * 60_000, }, }, costBudget: { perCall: 0.05, perSession: 0.50, onExceed: 'throw', }, approvals: { timeoutMs: 5 * 60_000 }, async run({ init, input }) { const agent = await init(); const session = await agent.session(); return session.prompt(`Create the report for ${input.accountId}.`); }, }); ``` Run and inspect it: ```sh fh describe governed-report fh run governed-report --account-id acct-42 ``` ## Precedence rules Definition controls are defaults with security-aware composition: | Control | Behavior when `init({...})` also supplies a value | | --- | --- | | `instructions` | A call-provided `role` wins. Otherwise instructions become the agent role. | | `tools` and `skills` | Definition and call values are combined. | | `policy` | Definition policy is a security floor. Calls may add denials and approvals, but cannot replace definition allowlists. | | `costBudget` | The lower per-call and per-session limits win. A call cannot raise the definition budget. | | `approvals.timeoutMs` | The shorter definition/call timeout wins. | For example, this call can narrow the job but cannot enable network or raise its budget: ```ts const agent = await init({ policy: { toolPolicy: { deny: ['lookup_account'] }, }, costLimit: { perSession: 0.25 }, }); ``` ## Adversarial controls For outbound HTTP, enable DNS answer verification in Node deployments so an allowed hostname cannot resolve to loopback, link-local, metadata, or RFC1918 space: ```ts const policy = { network: { mode: 'allowlist', hosts: ['api.example.com'], protocols: ['https:'], resolveDns: true, }, }; ``` `policiedFetch()` checks the original URL, each redirect, IP literals, and DNS answers. Production containers should also enforce egress at the network layer because process-level policy cannot prevent code that deliberately bypasses the configured fetch implementation. Tool aliases declare `policyAlias` so policy evaluates their canonical capability. For example, a `save_file` tool with `policyAlias: 'write'` remains subject to every `write` deny and approval rule. Raw access through `session.sandbox` cannot bypass `requireApproval`; only the exact operation inside the resolved approval scope receives a temporary grant. Durable attachment materialization defaults to 20 attachments, 10 MiB per decoded attachment, and 25 MiB total. `createSubmissionRunner({ attachments: { ... } })` can set lower `maxCount`, `maxAttachmentBytes`, and `maxTotalBytes` values for a route or tenant. ## Approval handlers The definition declares when approval is required and how long it may wait. The host supplies the operator integration: ```ts const agent = await init({ onApproval: async (request) => { const decision = await approvalService.waitForDecision(request); return decision; // approved or denied }, }); ``` Temporal-backed sessions persist approval waits. Inline sessions require the process to remain available. See [Approvals](/docs/building/approvals) for CLI and API resolution. ## Tenant-wide budgets Use a shared budget store when limits must survive restarts or span replicas: ```ts import { tenantCostLimit } from '@fabric-harness/sdk'; import { postgresCostBudgetStore } from '@fabric-harness/node'; const costLimit = tenantCostLimit('acme', { perDayUsd: 50, store: postgresCostBudgetStore({ client: pool }), }); const agent = await init({ tenantId: 'acme', costLimit }); ``` Definition budgets protect one job/session. Shared budget stores enforce organization or tenant ceilings across processes. Postgres and SQLite use atomic reservations: concurrent calls that would cross the ceiling are rejected without committing an over-budget total. Bind scheduled background jobs to a service principal instead of running them without identity: ```ts await startDevServer({ scheduler: { identity: async (jobName) => ({ tenantId: 'acme', actor: { principal: { id: `scheduler:${jobName}`, type: 'service' } }, }), }, }); ``` Channel adapters derive tenant and actor from the verified provider payload. HTTP principals cannot list, read, abort, delete, or vote on resources owned by another tenant. ## See also - [Policies and approvals](/docs/reference/policies-approvals) - [Cost attribution](/docs/operating/cost-attribution) - [Agent anatomy](/docs/building/anatomy) - [Security hardening](/docs/reference/security-hardening) --- # Evaluations Canonical: https://harness.fabric.pro/docs/building/evals Test jobs with deterministic scorers and model-based judges. Fabric eval suites are TypeScript modules named `*.eval.ts`. Each suite supplies cases, a runner, one or more scorers, and an optional pass threshold. `fh test` discovers and runs them locally or in CI. ## Install ```sh pnpm add -D @fabric-harness/evals @fabric-harness/node ``` ## Evaluate a job ```ts title="evals/hello.eval.ts" import { containsTextScorer, defineEvalSuite } from '@fabric-harness/evals'; import { runAgent } from '@fabric-harness/node'; export default defineEvalSuite({ name: 'hello-quality', cases: [ { id: 'named-user', input: { name: 'Ada' }, expected: 'Ada' }, { id: 'default-user', input: { name: 'World' }, expected: 'World' }, ], runner: async ({ case: evalCase }) => { const run = await runAgent({ agent: 'hello', payload: evalCase.input, mock: true, }); return run.result; }, scorers: [containsTextScorer()], passThreshold: 1, }); ``` Run every suite: ```sh fh test ``` Run a subset or produce machine-readable output: ```sh fh test evals --suite hello-quality fh test --threshold 0.9 --json ``` The command exits nonzero when any suite fails, so it can be used directly as a CI quality gate. ## Choose scorers | Scorer | Use it for | | --- | --- | | `exactMatchScorer()` | Deterministic structured or scalar output. | | `containsTextScorer()` | Required text in an answer. | | `regexMatchScorer()` | IDs, formats, and constrained strings. | | `jsonShapeMatchScorer()` | Required JSON fields and primitive types. | | `llmAsJudgeScorer()` | Semantic criteria that deterministic assertions cannot express. | Prefer deterministic scorers first. For an LLM judge, use a separate provider/model, write a narrow rubric, and set `passThreshold` explicitly. Never pass provider secrets through eval case input. For long-lived comparisons and stored grading trajectories, see [Jetty](/docs/ecosystem/tooling/jetty). For every exported type and scorer, see the [eval library reference](/docs/reference/eval-library). --- # HTTP applications Canonical: https://harness.fabric.pro/docs/building/http-applications Mount authenticated Fetch routes and middleware around Fabric jobs, agents, channels, health, and identity. Use the application surface when one deployed service needs Fabric routes plus product-specific HTTP endpoints. Routes and middleware live in `.fabricharness/config.ts`, so `fh dev` and Node-derived build artifacts run the same application. ```mermaid flowchart LR REQ[HTTP request] --> PUB[Public middleware] PUB --> SYS[Health, rate limit, and authentication] SYS --> RBAC[Principal, tenant, and permission] RBAC --> APP[Authenticated middleware] APP --> CHOICE{Route owner} CHOICE -->|Application| CUSTOM[Fetch route handler] CHOICE -->|Fabric| CORE[Job, agent, channel, MCP, or admin] CUSTOM --> RES[HTTP response] CORE --> RES RES --> APP APP --> PUB classDef public fill:#f3f4f6,stroke:#6b7280,color:#111827 classDef identity fill:#fef3c7,stroke:#d97706,color:#422006 classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef custom fill:#dcfce7,stroke:#16a34a,color:#052e16 class PUB public class SYS,RBAC identity class CORE fabric class CUSTOM custom ``` ## Define routes ```ts title=".fabricharness/config.ts" import { defineApplication, type FabricHarnessConfig } from '@fabric-harness/node'; const application = defineApplication({ routes: [ { method: 'POST', path: '/api/customers/:customerId/message', permission: 'agent:invoke', async handler(request, context) { const body = await request.json() as { message: string; deliveryId?: string; }; const receipt = await context.dispatch( { agent: 'support', id: context.params.customerId!, message: { kind: 'user', body: body.message }, }, body.deliveryId ? { idempotencyKey: body.deliveryId } : undefined, ); return Response.json(receipt, { status: 202 }); }, }, { method: 'POST', path: '/api/reports', permission: 'agent:invoke', async handler(request, context) { const input = await request.json(); return Response.json( await context.invoke({ job: 'report', input }), { status: 202 }, ); }, }, ], }); export default { application, } satisfies FabricHarnessConfig; ``` Route paths support named `:parameters` and a trailing `*` wildcard. The handler uses the standard Web `Request` and returns a Web `Response`. Its context contains: | Field | Purpose | | --- | --- | | `principal`, `actor`, `tenantId` | Server-validated identity and tenant; request bodies cannot replace them. | | `params` | Decoded named path parameters. | | `dispatch()` | Durable admission to a persistent agent with optional idempotency key. | | `invoke()` | Ambient finite-job admission with tenant, actor, and parent correlation. | | `stores` | The configured session, submission, conversation, and attachment stores. | | `signal` | Aborts when the client aborts the request. | | `requestId` | Correlation id for application logs and response headers. | Custom routes inherit the normal authentication, tenant binding, rate limit, and authorization pipeline. Set `permission` to the closest built-in permission. It defaults to `session:read` for `GET` and other non-Fabric paths. Application hooks do not accept identity from payload fields. ## Add middleware Authenticated middleware receives the complete request context and wraps both custom and built-in routes. Calling `next()` once continues the chain. For a custom route, `next()` returns its `Response`, so middleware can add headers or replace it. For a streaming or built-in Node route, `next()` completes when the response finishes. ```ts middleware: [async (request, context, next) => { const started = performance.now(); const response = await next(); await audit.record({ requestId: context.requestId, principalId: context.principal.id, tenantId: context.tenantId, method: request.method, path: new URL(request.url).pathname, durationMs: performance.now() - started, }); if (!response) return; const headers = new Headers(response.headers); headers.set('x-fabric-request-id', context.requestId); return new Response(response.body, { status: response.status, headers }); }], ``` `publicMiddleware` wraps health, rate limiting, and authentication. It intentionally receives no principal or stores. Use it for request timing, trusted ingress normalization, and global response headers, not authorization decisions. ```ts publicMiddleware: [async (request, context, next) => { const response = await next(); console.log(context.requestId, request.method, new URL(request.url).pathname); return response; }], ``` Middleware and handlers share one cached request body. Reading `request.clone().json()` in middleware does not consume the bytes later used by job, channel, webhook-signature, or custom route handling. The configured `maxBodyBytes` limit still applies. ## Route ownership Fabric reserves `/health`, `/ready`, `/jobs`, `/agents`, `/channels`, `/sessions`, `/runs`, `/mcp`, `/admin`, `/builds`, `/dispatch`, and `/openapi.json`. A custom route beneath a reserved root or a duplicate method/path fails before the server listens. This prevents an application update from silently replacing approval, health, or agent behavior. The complete runnable workspace is in [`examples/application-routes`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/application-routes). --- # Model Providers Canonical: https://harness.fabric.pro/docs/building/model-providers Configure and select LLM providers per agent, session, or call. Fabric Harness uses an explicit `provider/model-id` reference everywhere a model is selected. There is no implicit "default OpenAI" — you opt into a provider by configuring credentials and naming the model. ## Reference format ``` provider/model-id ``` Examples: ``` openai/gpt-4o anthropic/claude-sonnet-4-6 gemini/gemini-2.5-pro bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0 ``` ## Setting credentials Put provider keys once in a repo/workspace `.env.local`; Fabric Harness auto-loads `.env` and `.env.local` files, while shell env still wins: ```sh cp .env.example .env.local # OPENAI_API_KEY=... # ANTHROPIC_API_KEY=... # AZURE_OPENAI_ENDPOINT=https://....openai.azure.com # AZURE_OPENAI_API_KEY=... ``` Use explicit `--env ` only for overrides. Never paste API keys into source files or session artifacts. ## Selecting the model The first non-empty wins, in this order: 1. CLI flag: `fh run ask --model openai/gpt-5.5` 2. Environment: `FABRIC_MODEL=openai/gpt-5.5` 3. `.fabricharness/config.ts` → `run.model` or `agent.model` 4. Agent-declared default: `agent({ model: 'openai/gpt-5.5' })` Per-call override: ```ts await session.prompt('Summarize', { model: 'openai/gpt-5.5' }); ``` ## Mock provider For local development and tests, `openai/gpt-5.5` returns deterministic stub responses. It honors the typed-result schema where possible. ```ts export default agent({ // ... model: process.env.FABRIC_MODEL ?? 'openai/gpt-5.5', }); ``` ## Provider env names Fabric Harness knows the standard env names for common providers: - `OPENAI_API_KEY` - `ANTHROPIC_API_KEY` - `OPENROUTER_API_KEY` - `GEMINI_API_KEY` - `GOOGLE_API_KEY` - `AZURE_OPENAI_API_KEY` + `AZURE_OPENAI_ENDPOINT` - `GROQ_API_KEY` - `MISTRAL_API_KEY` - `COHERE_API_KEY` ## Cloudflare Workers AI binding When deploying to Cloudflare Workers with `fh build --target cloudflare`, you can route inference through the platform binding (`env.AI.run()`) instead of HTTP — no API tokens, no egress, runs at the edge. ```ts import { CloudflareWorkersAIModelProvider } from '@fabric-harness/cloudflare/workers-ai'; export default { async fetch(request: Request, env: Env) { const fabric = await init({ modelProvider: new CloudflareWorkersAIModelProvider({ binding: env.AI, defaultModel: '@cf/meta/llama-3.1-8b-instruct', }), }); // ... }, }; ``` `wrangler.toml`/`jsonc`: ```toml [ai] binding = "AI" ``` Handles modern `{ choices: [...] }` and legacy `{ response: '...' }` Workers AI shapes. Optional Cloudflare AI Gateway routing supports enterprise logging/routing knobs: ```ts new CloudflareWorkersAIModelProvider({ binding: env.AI, defaultModel: '@cf/meta/llama-3.1-8b-instruct', gateway: { id: 'prod-gateway', skipCache: false, cacheTtl: 3600, collectLog: true, eventId: request.headers.get('x-request-id') ?? undefined, metadata: { tenant: 'acme', environment: 'prod' }, }, models: { '@cf/meta/llama-3.1-8b-instruct': { contextWindowTokens: 8192, maxOutputTokens: 2048, supportsTools: true, }, }, }); ``` Model metadata feeds context-budgeting/auto-compaction and admin UIs. The provider includes built-in metadata for common Workers AI chat models and accepts `models` / `defaultModelInfo` overrides for private or newly released models. ## OpenAI-compatible gateways Many AI gateway products speak the OpenAI Chat Completions request/response shape: Vercel AI Gateway, Helicone, Portkey, LiteLLM (self-hosted), internal corp proxies. Wire any of them with the generic `aiGateway()` helper: ```ts import { aiGateway, init } from '@fabric-harness/sdk'; // Helicone const fabric = await init({ modelProvider: aiGateway({ baseUrl: 'https://oai.helicone.ai/v1', apiKey: process.env.OPENAI_API_KEY!, headers: { 'Helicone-Auth': `Bearer ${process.env.HELICONE_API_KEY}` }, defaultModel: 'gpt-4o', name: 'helicone', }), }); // Self-hosted LiteLLM const fabric = await init({ modelProvider: aiGateway({ baseUrl: 'http://litellm.internal:4000/v1', apiKey: process.env.LITELLM_KEY!, defaultModel: 'azure/gpt-4o', }), }); ``` ### Vercel AI Gateway preset [Vercel AI Gateway](https://vercel.com/docs/ai-gateway/sdks-and-apis/openai-compat) gets a thin preset with the gateway URL pre-baked: ```ts import { vercelAIGateway, init } from '@fabric-harness/sdk'; const fabric = await init({ modelProvider: vercelAIGateway({ apiKey: process.env.AI_GATEWAY_API_KEY!, defaultModel: 'anthropic/claude-3-5-sonnet-20241022', }), }); ``` `baseUrl` defaults to `https://ai-gateway.vercel.sh/v1`; override for staging or self-hosted. ## Foundry runtime (Azure) On Azure compute such as an ACA Job, AKS pod, or VM with managed identity, `FoundryRuntimeModelProvider` calls the Foundry-managed Azure OpenAI surface using a Bearer token instead of an API key: ```ts import { FoundryRuntimeModelProvider } from '@fabric-harness/azure/foundry-runtime'; import { init } from '@fabric-harness/sdk'; const fabric = await init({ modelProvider: new FoundryRuntimeModelProvider({ defaultModel: 'gpt-4o', }), }); ``` Set `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_DEPLOYMENT`. Supply `FOUNDRY_AGENT_TOKEN` when your host injects one, or install the optional `@azure/identity` peer dependency to use `DefaultAzureCredential` with the workload's managed identity. ## Spend caps Per-call and per-session USD ceilings prevent runaway spend. Wired through `init({ costLimit })`: ```ts const fabric = await init({ costLimit: { perCall: 0.10, // throw if a single model call exceeds $0.10 perSession: 1.00, // throw if cumulative session spend exceeds $1.00 onExceed: 'throw', // 'throw' (default) | 'approve' }, }); ``` When `onExceed: 'approve'` the loop pauses on a violation and emits `approval_requested` with `kind: 'cost-limit'`. Approve via your existing approval UI (or `fh approve `) to release the loop; deny to throw `CostLimitExceededError`. Limits evaluate after each call's cost lands on `usage.costUsd`. Forks and replays start with a fresh budget — replay is a debug action, not production work. ### Cross-process aggregation For "tenant X spends ≤ $50/day" or "company-wide ≤ $100/hour" caps, pair `perScope + scopeKey + store` with a cross-process `CostBudgetStore`: ```ts import { init, inMemoryCostBudgetStore } from '@fabric-harness/sdk'; import { postgresCostBudgetStore } from '@fabric-harness/node'; const fabric = await init({ costLimit: { perScope: 50.00, scopeKey: `tenant:${tenantId}:day:${todayIso}`, store: postgresCostBudgetStore({ client: pgClient }), // or inMemoryCostBudgetStore() for single-process onExceed: 'throw', }, }); ``` The store is the source of truth — multiple agents / multiple processes share the running total. fabric-harness never interprets `scopeKey`; you pick the convention (per-tenant, per-day, per-org). Reset semantics (daily rollover, billing period close) are also yours — call `store.reset(scopeKey)` from a scheduled task. ## Anthropic prompt caching When an Anthropic response includes `cache_read_input_tokens` / `cache_creation_input_tokens`, fabric-harness records them on `usage.cachedInputTokens` and `usage.cacheWriteTokens`, and the cost calculator discounts billed input tokens by the cached read amount (and adds the cache-write surcharge when present). `fh metrics` shows a new `Cache: read=N write=N` line so you can see how much you're saving. ```text $ fh metrics ask-1f4f... Tokens: input=120000 output=2400 total=122400 Cache: read=96000 write=0 Cost: $0.046800 ``` Cache-read tokens are billed at ~10% of the standard input rate on Claude models. Long, stable system prompts → big savings. ## Per-call cost telemetry Every model call is enriched with a USD estimate from a static price table (mainline OpenAI, Anthropic, Gemini, Bedrock, Cohere). Cost shows up in `fh metrics` and on OpenTelemetry spans as `gen_ai.usage.cost_usd` — see [CLI → metrics](/docs/cli/sessions#fh-metrics). Override or extend the catalog at runtime when you have custom-rate contracts: ```ts import { registerModelPrices } from '@fabric-harness/sdk'; registerModelPrices([ { provider: 'openai', model: 'gpt-4o', inputPerMTok: 1.5, outputPerMTok: 6, effectiveAt: '2026-05-08', notes: 'Enterprise contract' }, ]); ``` ## Reasoning effort Reasoning-capable models accept a `thinkingLevel` controlling how much the model thinks before answering: ```ts type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'; ``` It is configurable at three scopes, most-specific wins: ```ts const agent = await init({ model: 'cloudflare/@cf/openai/gpt-oss-120b', thinkingLevel: 'medium' }); const session = await agent.session('s1', { thinkingLevel: 'high' }); // per-session override await session.prompt('think hard about this', { thinkingLevel: 'xhigh' }); // per-call override ``` The level is **capability-gated**: reasoning-capable providers map it to their native control, others ignore it (no error). `'off'` (or unset) requests no reasoning. - **Default loop (pi-agent-core):** works for every provider; pi-ai handles capability detection + per-provider mapping and clamps the level to what each model supports. - **Native Fabric providers:** Cloudflare Workers AI binding & OpenAI-compatible map to `reasoning_effort`; Anthropic to `thinking.budget_tokens` (with `max_tokens` raised above the budget); Gemini/Vertex to `thinkingConfig.thinkingBudget`. Gated to known reasoning families (o-series / gpt-5 / gpt-oss, Claude 3.7/4.x, Gemini 2.5). --- # Persistent Agents & Dispatch Canonical: https://harness.fabric.pro/docs/building/persistent-agents Long-lived, addressable agent instances with cross-call sessions, async dispatch, and a streaming WebSocket conversation. Fabric Harness has two authoring surfaces. A **job** is a finite, run-once execution; a **persistent agent** is a long-lived, URL-addressable instance whose sessions continue across calls. (Fabric names the finite surface *job* because *workflow* is reserved for Temporal durable execution.) | | Finite **job** | Persistent **agent** | | --- | --- | --- | | Author with | `agent({ run })` (or `job({ run })`) | `createAgent(({ id, env }) => config)` | | Directory | `.fabricharness/jobs/` | `.fabricharness/agents/` | | Invoke | `POST /jobs/:name` → `{ result, runId }` | `POST /agents/:name/:id` with `{ message, session? }` | | Lifetime | one run, returns a result | long-lived instance; sessions persist across calls | | Async / streaming | tracked under `/runs` | `dispatch()` + `GET /agents/:name/:id` WebSocket | > **Fabric Harness 2.x:** the directory and route split is enforced. Finite definitions in `.fabricharness/agents/` are rejected, and invoking a finite job through `POST /agents/:name/:id` returns `410`. Move finite code to `.fabricharness/jobs/` and invoke it through `POST /jobs/:name`. ## Defining a persistent agent A persistent agent module default-exports `createAgent(...)`. The initializer receives the instance `id` and returns runtime configuration (resolved fresh per interaction): ```ts import { createAgent } from '@fabric-harness/sdk'; export default createAgent(({ id }) => ({ model: 'anthropic/claude-sonnet-4-6', instructions: `You are a long-lived assistant for ${id}.`, })); ``` `PersistentAgentConfig` mirrors the agent-level slice of `init()` — `model`, `tools`, `skills`, `sandbox`, `policy`, `cwd`, `thinkingLevel`, `compaction` — plus `instructions` (the session system prompt) and `subagents` (named roles). ## Direct prompts `POST /agents/:name/:id` durably admits a message for the instance's named session and returns `202 { submissionId, streamUrl, offset }`. Processing is FIFO per session and continues off the request. Add `?wait=true` only when a synchronous compatibility response is required: ```sh # Same instance "u1" → one continuing conversation curl -XPOST localhost:4317/agents/assistant/u1 -d '{"message":"remember my name is Ada"}' curl -XPOST localhost:4317/agents/assistant/u1 -d '{"message":"what is my name?"}' ``` Read settlement at `/agents/:name/:id/submissions/:submissionId`, catch up through `/conversation?offset=`, or tail `/stream?offset=`. Concurrent messages to one instance session queue FIFO instead of returning `409`. ## Async dispatch `dispatch()` hands an input to an instance for asynchronous processing, returning a receipt immediately: ```ts import { dispatch } from '@fabric-harness/sdk'; const receipt = await dispatch({ agent: 'assistant', id: 'u1', input: 'summarize today' }); // { dispatchId, acceptedAt } ``` Over HTTP, `POST /agents/:name/:id/dispatch` returns `202` + the receipt: ```sh curl -XPOST localhost:4317/agents/assistant/u1/dispatch -d '{"input":"summarize today"}' ``` Dispatch processing is **idempotent by `dispatchId`** (a re-delivered dispatch is applied at most once). The `fh dev` server uses an in-process queue; with `runtime: 'temporal'`, durable delivery uses `temporalDispatchQueue` — dispatches survive worker/process restarts. ## Streaming conversation (WebSocket) `GET /agents/:name/:id` upgrades to a conversational WebSocket: ```ts const ws = new WebSocket('ws://localhost:4317/agents/assistant/u1'); // server → { type: 'ready', target: 'agent', name, instanceId } ws.send(JSON.stringify({ type: 'prompt', requestId: 'r1', message: 'hello' })); // server → { type: 'started', requestId } // → { type: 'event', requestId, event } (streamed, repeated) // → { type: 'result', requestId, result, session } ``` Send `{ type: 'ping' }` for a `pong` heartbeat. Prompts on one connection are serialized. ## Jobs and persistent agents Use a finite job for one typed invocation and a persistent agent for an addressable conversation. Both share the same session, tool, policy, sandbox, and deployment surfaces. --- # React applications Canonical: https://harness.fabric.pro/docs/building/react Build agent conversations and finite-run interfaces with the public Fabric client protocol. `@fabric-harness/react` provides `FabricProvider`, `useFabricClient`, `useFabricAgent`, and `useFabricJob`. The package talks only to the public authenticated HTTP protocol, so the same UI can target local development, a Node artifact, Databricks Apps, or another compatible deployment. ```sh pnpm add @fabric-harness/react @fabric-harness/client react ``` ## Configure the provider Create one client for the application. Authentication headers stay in the application transport and are not placed in agent messages or model context. ```tsx title="src/main.tsx" import { FabricProvider } from '@fabric-harness/react'; export function Root({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` For server-rendered frameworks, pass a request-scoped client through `client`. Hooks use stable external-store server snapshots and start network observation only after the component mounts. ## Agent conversations `useFabricAgent` catches up from offset zero, follows new records, deduplicates reconnect replay, and applies truncation records to the canonical transcript. Optimistic messages remain separate in `pending` until their submission-correlated input is observed. ```tsx import { useFabricAgent } from '@fabric-harness/react'; function SupportChat({ customerId }: { customerId: string }) { const chat = useFabricAgent({ agent: 'support', id: customerId, session: 'default' }); async function send(text: string) { const submissionId = await chat.send(text); const settled = await chat.wait(submissionId, { timeoutMs: 30_000 }); console.log(settled.outcome); } return ( <> {chat.transcript.map((entry) =>

{String(entry.data?.text ?? '')}

)} {chat.pending.map((message) => ( ))} ); } ``` Send image attachments with the normal delivered-message shape: ```ts await chat.send({ kind: 'user', body: 'Inspect this screenshot', attachments: [{ type: 'image', mimeType: 'image/png', data: base64 }], }); ``` ## Finite runs `useFabricJob` admits an asynchronous run, follows its offset events, publishes the terminal run envelope, and exposes retry and abort. Starting a newer invocation supersedes the previous observation, so a late terminal response from the old run cannot replace the new run's state. Use `invokeSync` only for short request/response work. ```tsx const report = useFabricJob<{ accountId: string }, { url: string }>('account-report'); await report.invoke( { accountId: 'account-42' }, { idempotencyKey: 'account-report:account-42' }, ); console.log(report.status, report.run?.output?.url); await report.abort(); ``` Agent observation reconnects with bounded exponential backoff and resets the delay after receiving a record. Stores with no subscribers or in-flight work are evicted, which keeps long-lived applications with dynamic agent and job addresses bounded. ## Runnable application [`examples/react-chat`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/react-chat) starts `fh dev --mock` and Vite with one `pnpm dev` command. It includes multiple persistent agent instances, image attachments, optimistic retry, abort, canonical transcript streaming, and a finite job panel. Its automated browser check captures desktop/mobile layouts and fails on browser errors or mobile horizontal overflow. --- # Roles Canonical: https://harness.fabric.pro/docs/building/roles System-prompt overlays per agent, session, or call. Roles live under `.fabricharness/roles/.md`. They are **system-prompt overlays**, not persisted user messages. ## Format ```md --- description: Senior backend engineer focused on safe, minimal changes. model: openai/gpt-5.5 --- You are a senior backend engineer. Prefer small, well-tested changes. Do not make broad refactors unless required. ``` ## Precedence ``` call role > session role > agent role ``` ```ts // Agent default const fabricAgent = await init({ role: 'engineer' }); // Session-scoped override (first arg is the session id; pass undefined to auto-generate) const session = await fabricAgent.session(undefined, { role: 'reviewer' }); // Call-scoped override await session.prompt('Review this PR', { role: 'reviewer' }); ``` ## Why a role and not just a prompt A role is reusable, scoped, and never appears in the user-message history that the model sees. That keeps the conversation clean and prevents role text from being summarized away during compaction. --- # Sandbox connectors Canonical: https://harness.fabric.pro/docs/building/sandbox-connectors Modular remote sandbox adapters for Daytona, E2B, Modal, and custom providers. Fabric Harness treats every execution backend as a `SandboxEnv`: a small interface for shell execution, file IO, path scoping, cleanup, and optional snapshots. Agent/session/runtime code does not need to know whether work is running in the virtual sandbox, local process, Docker, Daytona, E2B, Modal, Cloudflare, Foundry, Kubernetes, or another provider. ## Contract A remote provider adapter implements `RemoteSandboxApi` and wraps it with `createRemoteSandboxEnv`: ```ts import { createRemoteSandboxEnv } from '@fabric-harness/sdk'; import type { RemoteSandboxApi } from '@fabric-harness/sdk'; const api: RemoteSandboxApi = { async exec(command, options) { /* provider shell call */ }, async readFile(path) { /* UTF-8 file read */ }, async readFileBuffer(path) { /* binary file read */ }, async writeFile(path, content) { /* file write */ }, async stat(path) { /* file stat */ }, async readdir(path) { /* list names */ }, async exists(path) { /* existence check */ }, async mkdir(path, options) { /* mkdir */ }, async rm(path, options) { /* remove */ }, }; export const sandbox = createRemoteSandboxEnv(api, { cwd: '/workspace' }); ``` Provider SDK objects and credentials stay in your app code. Fabric only receives file paths, bytes, commands, cwd, env, and timeout values. ## Package adapters `@fabric-harness/connectors` ships dependency-free, structural adapters. Your app owns the provider SDK dependency and passes an initialized sandbox object into Fabric. ```sh npm install @fabric-harness/connectors ``` The connector package declares provider SDKs as optional peers, so applications install only the provider they use. | Provider | Compatible SDK range | Contract-tested version | |---|---|---| | Daytona | `@daytona/sdk >=0.195.0 <1` | `0.195.0` | | E2B | `@e2b/code-interpreter >=2.6.1 <3` | `2.6.1` | | Modal | `modal >=0.9.0 <1` | `0.9.0` | | Vercel | `@vercel/sandbox >=1.10.1 <2` | `1.10.1` | | Kubernetes | `@kubernetes/client-node >=0.21.0 <0.22` | `0.21.0` | | Cloudflare Sandbox | `@cloudflare/sandbox >=0.9.2 <1` | `0.9.2` | | Cloudflare Shell | `@cloudflare/shell >=0.3.7 <0.4` | `0.3.7` | ### Daytona ```ts import { Daytona } from '@daytona/sdk'; import { daytonaSandbox } from '@fabric-harness/connectors'; const client = new Daytona({ apiKey: process.env.DAYTONA_API_KEY }); const remote = await client.create({ image: 'ubuntu:latest' }); const fabric = await init({ sandbox: daytonaSandbox(remote, { cleanup: true }), }); ``` The adapter maps Daytona filesystem/process calls to Fabric's `SandboxEnv` and uses Daytona's workdir when available through `daytonaSandboxFactory()`. ### E2B ```ts import { Sandbox } from '@e2b/code-interpreter'; import { e2bSandbox } from '@fabric-harness/connectors'; const remote = await Sandbox.create(); const fabric = await init({ sandbox: e2bSandbox(remote, { cleanup: true }), }); ``` If your E2B package exposes a different class, adapt it to the structural `E2BSandboxLike` shape or wrap it in `remoteSandboxEnv()`. ### Modal Fabric maps the native Modal TypeScript SDK sandbox directly. The structural `modalSandbox()` helper remains available for custom provider handles. ```ts import { ModalClient } from 'modal'; import { modalSdkSandbox } from '@fabric-harness/connectors/modal'; const client = new ModalClient(); const app = await client.apps.fromName('fabric-harness', { createIfMissing: true }); const image = client.images.fromRegistry('node:22-alpine'); const remote = await client.sandboxes.create(app, image, { workdir: '/workspace' }); const fabric = await init({ sandbox: modalSdkSandbox(remote, { cleanup: true }) }); ``` ## Generic adapter Use `remoteSandbox()` to produce a reusable `SandboxFactory`: ```ts import { remoteSandbox } from '@fabric-harness/connectors'; export function providerSandbox(client): SandboxFactory { return remoteSandbox({ exec: (command, options) => client.exec(command, options), readFile: (path) => client.readFile(path), readFileBuffer: (path) => client.readFileBuffer(path), writeFile: (path, content) => client.writeFile(path, content), stat: (path) => client.stat(path), readdir: (path) => client.readdir(path), exists: (path) => client.exists(path), mkdir: (path, options) => client.mkdir(path, options), rm: (path, options) => client.rm(path, options), }, { workspacePath: '/workspace' }); } ``` ## Stream command output `SandboxExecOptions` exposes the same output callbacks for local, Docker, and remote sandboxes: ```ts const result = await env.exec('npm test', { timeout: 120_000, onStdout: (chunk) => process.stdout.write(chunk), onStderr: (chunk) => process.stderr.write(chunk), }); ``` E2B, Vercel, and Kubernetes forward provider output as it arrives. Daytona's compatible command API returns collected output, so the adapter invokes the callbacks immediately before the command promise settles. Custom and Modal adapters emit incremental chunks through the same options. Resource and egress enforcement belongs in the provider's sandbox creation call. Configure CPU, memory, image, network blocking, and domain allowlists before passing the provider object to Fabric; use Fabric `policy` for tool, command, filesystem, and application-level network decisions inside the session. ## Certification helper Use the full contract runner before deploying a provider adapter: ```ts import { assertSandboxCertification } from '@fabric-harness/connectors'; const env = daytonaSandbox(remote, { cleanup: true }); const report = await assertSandboxCertification(env, { provider: 'daytona', sdkPackage: '@daytona/sdk', sdkVersion: '0.195.0', credentialed: true, reconnect: async (ref) => daytonaSandbox( await client.get((ref.providerData as { workspaceId: string }).workspaceId), ), verifyCleanup: async () => { /* assert the workspace was deleted */ }, }); ``` The runner verifies: 1. POSIX shell execution and output 2. exact binary file round trips 3. working directory and environment forwarding 4. timeout exit `124` and abort exit `130` 5. stdout/stderr callbacks 6. JSON-safe portable references and cross-client reconnect 7. provider cleanup verification The returned `SandboxCertificationReport` is safe to retain as CI evidence: it records provider, SDK version, timings, and check outcomes without credentials or workspace content. ## Verify a provider connection Live tests are skipped unless enabled: ```sh FABRIC_DAYTONA_TEST=1 DAYTONA_API_KEY=... pnpm --filter @fabric-harness/connectors test FABRIC_E2B_TEST=1 E2B_API_KEY=... pnpm --filter @fabric-harness/connectors test FABRIC_MODAL_TEST=1 MODAL_TOKEN_ID=... MODAL_TOKEN_SECRET=... pnpm --filter @fabric-harness/connectors test ``` ## Connector recipes `fh add` still prints markdown recipes for project-local adapters: ```sh fh add fh add daytona | claude fh add https://e2b.dev --category sandbox | claude ``` Recipes are useful when the provider SDK version or organization conventions require custom code. Package adapters are better when your provider object matches the structural interfaces. ## Provider adapter checklist - Scope every path to the provider workspace root. - Honor `cwd`, `env`, and `timeout` on `exec`. - Convert text and binary content correctly. - Keep API keys and provider SDK objects outside model context/history. - Enforce provider-specific network/resource limits before launching work. - Implement `cleanup` for temporary sandboxes. - Implement `snapshot`/`restore` only when the provider supports it truthfully. - Add a unit test with a fake provider object. - Add a live test behind an env gate. --- # Sandboxes Canonical: https://harness.fabric.pro/docs/building/sandboxes Where shell commands and tool calls actually run — virtual, local, docker, cloudflare, daytona, modal, foundry-hosted, and remote SDK-backed targets. import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Every session has a sandbox — an isolated environment exposing filesystem, shell, and metadata APIs. Fabric Harness defines a backend-agnostic `SandboxEnv` interface and ships several implementations. ## The interface ```ts export interface SandboxEnv { exec(command: string, options?: { cwd?: string; env?: Record; timeout?: number; }): Promise; readFile(path: string): Promise; readFileBuffer(path: string): Promise; writeFile(path: string, content: string | Uint8Array): Promise; stat(path: string): Promise; readdir(path: string): Promise; exists(path: string): Promise; mkdir(path: string, options?: { recursive?: boolean }): Promise; rm(path: string, options?: { recursive?: boolean; force?: boolean }): Promise; cwd: string; resolvePath(path: string): string; snapshot?(): Promise; restore?(snapshot: SandboxSnapshot): Promise; cleanup(): Promise; } ``` ## Built-in sandboxes Start simple: the bare `@fabric-harness/sdk` agent wrapper defaults to `sandbox: 'virtual'`, so a first agent does not need Docker, Cloudflare, or a remote sandbox provider. | Backend | Use first when | Capability notes | | --- | --- | --- | | `virtual` (default for bare import) | You want the fastest no-container path for support bots, routing, tests, and lightweight filesystem/search work. | In-memory filesystem + bash-like shell via `just-bash`; no network; process-level isolation; no snapshot/restore. | | `local` | CI/repo automation intentionally needs host tools like `git`, `gh`, `npm`, or `python`. | Host process execution. Scope commands and secrets carefully. | | `docker` | The agent runs untrusted shell, data analysis, package installs, or generated code. | Container isolation; preferred production pilot sandbox for risky shell workloads. | | `cloudflare` | You deploy to Workers and need Cloudflare Sandbox containers per session. | Provider-managed container isolation with Durable Object session storage. | | `empty` | You only need model calls and custom tools; no filesystem or shell. | No shell/filesystem effects. | ## Decision matrix | Workload | Recommended sandbox | | --- | --- | | Hello world, support FAQ, routing, typed extraction | `virtual` | | GitHub issue triage in CI | `local` + scoped `defineCommand()` commands | | Data analysis over uploaded files | `docker` | | Full coding agent with Linux tools | Docker, Daytona, E2B, Modal, Kubernetes, or Cloudflare Sandbox | | Edge/serverless support agent | Cloudflare target + virtual/filesystem source, or Cloudflare Sandbox when real shell/container support is required | Treat `virtual` as a DX/performance feature, not a hard security boundary. Use Docker or a provider container/microVM for untrusted code. ## Selecting a sandbox The default import injects `sandbox: 'virtual'` automatically. Pick another by passing a value: ```ts import { agent } from '@fabric-harness/sdk'; import { getCloudflareSandbox } from '@fabric-harness/cloudflare'; export default agent({ run: async ({ init, env }) => { const sandbox = await getCloudflareSandbox(env.SANDBOX, 'session-1'); const session = await (await init({ sandbox })).session(); return await session.prompt('hello edge'); }, }); ``` **Example:** `examples/with-cloudflare-sandbox/`. Edge-deployed via `fabric-harness build --target cloudflare`. ```ts import { agent, schema } from '@fabric-harness/sdk/strict'; export default agent({ name: 'long-running', input: schema.object({ jobId: schema.string() }), run: async ({ init, input }) => { const fabric = await init({ runtime: 'temporal', sandbox: 'local', compaction: { enabled: false }, }); return await (await fabric.session()).prompt(`Process job ${input.jobId}`); }, }); ``` **Example:** `examples/with-temporal/`. Uses `/strict` because auto-compaction is non-deterministic across replays. ```ts import { agent } from '@fabric-harness/sdk'; import { AzureOpenAIModelProvider, createAzureKeyVaultSecretResolver } from '@fabric-harness/azure'; const provider = new AzureOpenAIModelProvider({ endpoint: process.env.AZURE_OPENAI_ENDPOINT!, apiKey: process.env.AZURE_OPENAI_API_KEY!, deployment: 'gpt-5.5', }); export default agent({ run: async ({ init, input }) => { const fabric = await init({ provider }); return await (await fabric.session()).prompt(input.prompt); }, }); ``` **Example:** `examples/with-azure/`. Build with `fabric-harness build --target foundry-hosted-agent`. ```ts import { agent } from '@fabric-harness/sdk'; import { daytonaSandbox } from '@fabric-harness/connectors'; import { Daytona } from '@daytona/sdk'; const client = new Daytona({ apiKey: process.env.DAYTONA_API_KEY }); export default agent({ run: async ({ init, input }) => { const remote = await client.create({ image: 'node:22' }); const fabric = await init({ sandbox: daytonaSandbox(remote, { cleanup: true }) }); return await (await fabric.session()).prompt(input.prompt); }, }); ``` **Example:** `examples/with-daytona/`. Daytona credentials stay in the Daytona client. ```ts import { agent } from '@fabric-harness/sdk'; import { ModalClient } from 'modal'; import { modalSdkSandbox } from '@fabric-harness/connectors/modal'; export default agent({ run: async ({ init, input }) => { const client = new ModalClient(); const app = await client.apps.fromName('fabric-harness', { createIfMissing: true }); const remote = await client.sandboxes.create(app, client.images.fromRegistry('node:22-alpine')); const sandbox = modalSdkSandbox(remote, { cleanup: true }); try { const fabric = await init({ sandbox }); return await (await fabric.session()).prompt(input.prompt); } finally { await sandbox.cleanup(); client.close(); } }, }); ``` **Example:** `examples/with-modal/`. Modal credentials remain in `ModalClient`. ```ts import { agent } from '@fabric-harness/sdk'; export default agent({ run: async ({ init, input }) => { const fabric = await init({ sandbox: { backend: 'docker', image: 'node:22', cleanup: true }, }); return await (await fabric.session()).prompt(input.prompt); }, }); ``` **Example:** `examples/with-docker/`. Per-session container isolation; good fit for coding agents. ## Sandbox vs runtime vs target These are three orthogonal axes: | Axis | Controls | Where it's set | |---|---|---| | **Sandbox** | Where shell commands and tool calls run. | `init({ sandbox })`. | | **Runtime** | How sessions persist. `stateless` / `inline` / `temporal`. | `init({ runtime })`. | | **Target** | The build artifact: Node process, Cloudflare Worker, Temporal worker, Foundry hosted agent. | `fabric-harness build --target `. | A Cloudflare Worker target most naturally pairs with the Cloudflare sandbox. A Temporal target most naturally pairs with `runtime: 'temporal'` and the `/strict` import. But the axes don't lock — you can deploy a Node target with a Daytona sandbox if you want. ## Remote and platform sandboxes `RemoteSandboxApi` keeps provider SDK types out of `@fabric-harness/sdk`, so application code can select a backend without changing the agent/session contract. | Integration path | Use it for | |---|---| | [`@fabric-harness/connectors`](/docs/building/sandbox-connectors) | Daytona, E2B, Modal, and provider-owned remote sandbox objects. | | [`@fabric-harness/azure/aks-sandbox`](/docs/deployment/azure#aks-sandbox-sandboxenv) | Pod-backed execution on AKS. | | [Databricks SQL sandbox](/docs/ecosystem/sandboxes/databricks-sql) | Governed SQL Warehouse execution; `session.shell()` executes SQL rather than bash. | | `remoteSandboxEnv()` | Kubernetes, microVM, or proprietary execution services that implement the common remote contract. | Use `fh add` when a provider needs project-specific lifecycle or SDK mapping. The [sandbox capability matrix](/docs/reference/sandboxes-matrix) shows how to inspect capabilities at runtime. --- # Session memory Canonical: https://harness.fabric.pro/docs/building/session-memory Persistent key/value recall across sessions, scoped by tenant. Distinct from session entries (audit log). `session.memory` lets agents remember facts across sessions — borrower preferences, prior outcomes, learned task history. It's a typed key/value store backed by `SessionMemory`, scoped automatically by the session's `tenantId`. ## Memory vs. entries | | `session.memory` | session entries (`fh export-audit`) | |---|---|---| | Purpose | Recall — facts the agent should remember | Audit — what the agent did | | Persistence | User-controlled key/value | Append-only log | | Visibility to model | Only when the agent reads + injects | Through compaction / replay | | Size | Bounded per key | Grows with each action | | Typical reads | Targeted: `memory.get('borrower:preferences')` | Full session view: `fh inspect`, `fh logs` | Memory writes do NOT land in the session log. Audit is unaffected. ## API ```ts const fabric = await init({ tenantId: 'tenant-acme', memory: postgresSessionMemory({ client: pgClient }), // or inMemorySessionMemory() }); const session = await fabric.session(); // Get / set typed values await session.memory.set({ key: 'borrower:42:preferences', value: { contactChannel: 'sms', timezone: 'America/Phoenix' }, ttlSeconds: 60 * 60 * 24 * 30, // optional 30-day TTL }); const entry = await session.memory.get<{ contactChannel: string }>('borrower:42:preferences'); // { key, value, tenantId, updatedAt, expiresAt?, metadata? } // List with prefix + recency filters const recent = await session.memory.list({ keyPrefix: 'borrower:42:*', limit: 10 }); await session.memory.delete('borrower:42:preferences'); ``` `session.memory` auto-scopes every operation to the session's `tenantId`. Two tenants writing to the same key get isolated values. ## Backends | Backend | Where | Use when | |---|---|---| | `inMemorySessionMemory()` | `@fabric-harness/sdk` | Tests, single-process pilots, ephemeral agents. | | `postgresSessionMemory({ client })` | `@fabric-harness/node` | Production. Survives restarts, shares across a fleet. | Postgres schema: ```sql CREATE TABLE fabric_harness_session_memory ( tenant_id TEXT NOT NULL DEFAULT '', key TEXT NOT NULL, value JSONB NOT NULL, metadata JSONB, expires_at TIMESTAMPTZ, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (tenant_id, key) ); ``` Created automatically on construction. Pass `initialize: false` if you manage migrations separately. ## Patterns **Borrower preferences (long-term).** Set on first contact; read on subsequent sessions to skip re-asking. ```ts const prefs = await session.memory.get<{ channel: 'sms' | 'email' }>('borrower:42:contact'); if (!prefs) { await session.prompt('Ask the borrower how they prefer to be contacted.'); await session.memory.set({ key: 'borrower:42:contact', value: { channel: 'sms' } }); } ``` **Task history (medium-term).** Use TTL so old context doesn't accumulate. ```ts await session.memory.set({ key: `task:${taskId}:summary`, value: { result, durationMs }, ttlSeconds: 60 * 60 * 24 * 7, // 7 days }); ``` **Cross-tenant guards.** Memory operations scope by `tenantId` automatically. The SDK never mixes tenants — `set({ key: 'k' })` from `tenant-a` and `tenant-b` produce isolated rows. ## Reset / cleanup Memory has no built-in expiry sweeper — TTL'd rows are filtered out on read. For high-volume deployments, run a periodic `DELETE FROM fabric_harness_session_memory WHERE expires_at < NOW()` from your scheduler. The `expires_at` column is indexed for that purpose. ## See also - [Multi-tenancy](/docs/operating/multi-tenancy) - [Audit export](/docs/operating/audit-export) --- # Sessions and Prompts Canonical: https://harness.fabric.pro/docs/building/sessions-prompts agent.session(), session.prompt(), and the harness loop. A session is a persisted message/context thread. Inside a session, you can run prompts, skills, tasks, and shell commands. ## Create a session ```ts const fabricAgent = await init(); const session = await fabricAgent.session(); // new id const resumed = await fabricAgent.session('s-001'); // resume by id const scoped = await fabricAgent.session('s-002', { role: 'engineer', model: 'openai/gpt-5.5', cwd: 'project', }); ``` ## `session.prompt(text, options?)` Run one harness loop turn — a single user prompt that may produce assistant messages, tool calls, and shell commands until the model returns a final answer. ```ts const answer = await session.prompt('What is Temporal?'); ``` ### Typed results Use `result` to validate and type the return value. The framework asks the model for a typed object and validates it against the schema. > `schema` is also exported from `@fabric-harness/sdk` — same API in both. ```ts import { schema } from '@fabric-harness/sdk'; const triage = await session.prompt('Triage this issue', { result: schema.object({ severity: schema.enum(['low', 'medium', 'high', 'critical']), summary: schema.string(), recommendedLabels: schema.array(schema.string()), }), }); ``` ### Streaming ```ts for await (const event of session.stream('Tell me about Temporal')) { if (event.type === 'text_delta') process.stdout.write(event.text); } ``` ### Working directories Set `cwd` at agent, session, prompt/skill/task, or shell scope. Relative `cwd` values are resolved inside the sandbox and cannot escape the sandbox workspace. ```ts const fabricAgent = await init({ cwd: 'project' }); const session = await fabricAgent.session(); await session.shell('npm install'); // runs in /workspace/project await session.shell('npm test', { cwd: 'ui' }); // runs in /workspace/project/ui await session.prompt('Inspect the UI package', { cwd: 'ui' }); await session.skill('review', { cwd: 'api', args: { focus: 'routes' } }); await session.task('Refactor tests', { cwd: 'packages/core' }); ``` File tools (`read`, `write`, `grep`, `glob`) use the same scoped cwd during prompts/skills/tasks, so coding agents can work in a repository subdirectory without repeating absolute paths. ### Tools and commands ```ts import { defineCommand } from '@fabric-harness/node'; const npm = defineCommand('npm'); const git = defineCommand('git'); await session.prompt('Run the failing tests and propose a fix', { commands: [npm, git], // tools: optional override of built-in tools }); ``` See [Tools](/docs/building/tools) and [Commands](/docs/building/commands). ## Reasoning streams Reasoning-capable models (Anthropic Claude with extended thinking, OpenAI o-series, Gemini 2.5 with thought summaries) emit a separate "thinking" content channel. Fabric Harness surfaces it on `ModelResponse.thinking` and emits a `text_delta` event with `kind: 'thinking'` so streaming consumers can render it apart from the final answer. ```ts const stream = session.stream('Plan the migration in detail.'); for await (const event of stream) { if (event.type === 'text_delta' && event.data?.kind === 'thinking') { renderThinking(event.data.delta); // grey/italic in your UI } else if (event.type === 'text_delta') { renderOutput(event.data?.delta); // normal model output } } ``` Provider notes: - **Anthropic**: thinking is emitted when the request includes `extended_thinking: true` (set via `headers` on `AnthropicModelProvider`). - **OpenAI o-series**: emitted automatically as `reasoning_content` on the chat completion message. - **Gemini 2.5**: emitted when `thinking_config` is set on the request; consumed via the same `thinking` field. Other providers leave `ModelResponse.thinking` undefined; consumers should fall back to `text_delta` without the `thinking` kind. ### Token-level streaming `OpenAICompatibleModelProvider` and `AnthropicModelProvider` both expose a `stream()` method that returns an `AsyncIterable`. The loop uses it automatically when present, emitting `text_delta` events as tokens arrive (instead of buffering until the response completes). UIs see word-by-word output; per-call cost telemetry still lands on the final aggregated chunk. Consumers don't need code changes — `session.stream()` already understands `text_delta`: ```ts for await (const event of session.stream(prompt)) { if (event.type === 'text_delta') process.stdout.write(event.data?.delta ?? ''); } ``` Mid-stream failures fall through to the loop's normal error path. Retries are NOT automatic for streamed calls — partial state can't be safely re-applied. ## `session.skill(name, options?)` Invoke a Markdown skill from `.fabricharness/skills//SKILL.md`. Args interpolate into the skill body, and `result` validates the typed return value. ```ts const triage = await session.skill('triage-issue', { args: { issueNumber: 42, repository: 'octocat/repo' }, commands: [gh], result: schema.object({ severity: schema.enum(['low', 'medium', 'high', 'critical']), }), }); ``` ## `session.task(text, options?)` Spawn a child task. Tasks are durable when the session runs on the Temporal worker target. ```ts const result = await session.task('Refactor the authentication middleware', { id: 'refactor-auth', result: schema.object({ filesChanged: schema.array(schema.string()) }), }); ``` ## `session.shell(command, options?)` Execute a shell command in the session's sandbox. ```ts const out = await session.shell('npm test', { cwd: '/workspace' }); ``` ## `session.fs` and `agent.fs` Use `session.fs` for host-side filesystem plumbing that should not appear in model history: staging input files, collecting scratch output, or checking whether generated artifacts exist. It uses the same sandbox backend and cwd as the session. ```ts const fabric = await init({ sandbox: 'virtual' }); const session = await fabric.session('build-123'); await session.fs.writeText('input/ticket.md', ticketBody); const exists = await session.fs.exists('input/ticket.md'); const files = await session.fs.list('input'); const text = await session.fs.readText('input/ticket.md'); ``` `agent.fs` is also available for quick setup scripts. It is backed by a lazily-created default session; prefer `session.fs` when you need explicit session identity, audit correlation, or lifecycle control. Filesystem aliases follow familiar sandbox conventions: `readFile`, `readFileBuffer`, `writeFile`, `readdir`, and `rm`. ## Session history ```ts const history = await session.history(); // { id, createdAt, updatedAt, entries, events } ``` The CLI's `fh inspect`, `fh logs`, and `fh metrics` are wrappers around this same data. --- # Skills Canonical: https://harness.fabric.pro/docs/building/skills Reusable Markdown procedures. Skills live under `.fabricharness/skills//SKILL.md`. They are Markdown-first: the body is the instructional prompt, the frontmatter declares metadata. ## Format ```md --- name: triage-issue description: Triage a GitHub issue and recommend severity, labels, and next action. model: openai/gpt-5.5 --- You are triaging a GitHub issue. Steps: 1. Read the issue using the `gh` command. 2. Inspect relevant files if available. 3. Determine severity. 4. Recommend labels. 5. Decide whether a fix can be proposed. ``` ## Invocation ```ts const result = await session.skill('triage-issue', { args: { issueNumber: 42, repository: 'octocat/repo' }, commands: [gh], result: schema.object({ severity: schema.enum(['low', 'medium', 'high', 'critical']), summary: schema.string(), recommendedLabels: schema.array(schema.string()), fixSuggested: schema.boolean(), }), }); ``` The framework: 1. Loads the skill by name. 2. Interpolates `args` into the prompt (template variables in the body, plus a structured arguments preamble). 3. Runs the harness loop with the chosen tools/commands. 4. Validates the typed result. ## Why skills, not just prompts - **Reuse.** Multiple agents can call the same skill. - **Versioning.** Skills are tracked in source control and can be distributed as packaged skill directories. - **Auditability.** Skill instructions are visible and reviewable, separate from agent code. - **Model overrides.** A skill can declare its own model (for cheap classifiers, big-context reviewers, etc.). --- # Tasks Canonical: https://harness.fabric.pro/docs/building/tasks Durable child or delegated agent runs. A **task** is a child or delegated agent run. Tasks let an agent split work into named, durable sub-runs that can be inspected, cancelled, and (on the Temporal target) replayed independently. ## Spawn a task ```ts const result = await session.task('Refactor the authentication middleware', { id: 'refactor-auth', result: schema.object({ filesChanged: schema.array(schema.string()), summary: schema.string(), }), }); ``` Options: | Option | Purpose | | --- | --- | | `id` | Stable id (good for resuming, dedup, and CLI inspection). | | `result` | Schema for typed output validation. | | `model` | Override the model for this task. | | `commands` | Scope shell commands available inside the task. | | `cwd` | Working directory for the child session's shell/file tools. | | `checkpoint` | Persist a checkpoint before and after the task (boolean or label). | ## Nesting limit (`MAX_TASK_DEPTH = 4`) A task can spawn its own task, which can spawn its own — up to **four levels deep**. The fifth nested call throws: ```ts // session.task → child.task → grandchild.task → great-grandchild.task ✓ // great-great-grandchild.task → throws "Task nesting exceeds MAX_TASK_DEPTH (4)" ``` The cap prevents runaway recursion. Each [`task_start` / `task_end`](/docs/reference/events) event carries a `depth: number` and a `parentSessionId`, so subscribers can render a tree: ```ts onEvent: (event) => { if (isEvent(event, 'task_start')) { console.log(`${' '.repeat(event.data.depth)}↳ ${event.data.taskId}`); } } ``` ## Inspect or cancel from the CLI ```sh fh tasks fh task refactor-auth fh cancel-task refactor-auth --actor preetham --reason "Replaced by manual fix" ``` ## Durable tasks on the Temporal target When the session runs on the Temporal worker target, each task becomes a child workflow. That gives you: - crash-safe execution, - retries for model and tool calls, - replayable history, - durable cancellation signals. ## Checkpoints inside a task Tasks can mark their own progress with checkpoints: ```ts await session.checkpoint.create({ label: 'before-fix' }); // ... risky work ... await session.checkpoint.create({ label: 'after-fix' }); ``` If the sandbox supports snapshots, the checkpoint records a snapshot ref so you can restore filesystem state along with the conversation state. --- # Testing Locally Canonical: https://harness.fabric.pro/docs/building/testing Mock model, doctor, dev server, examples, and assertions. The framework is designed so that "test the agent" doesn't mean "spin up a full LLM." Use the mock provider for fast, deterministic checks and reach for live models only when behavior depends on the model. ## Mock model ```sh fh run ask --question "hi" --mock ``` `--mock` injects the deterministic mock provider and still exercises input/output validation. Combine it with snapshot testing or schema-only assertions in your test suite. ## Vitest example ```ts import { describe, expect, it } from 'vitest'; import { runAgent } from '@fabric-harness/node'; describe('ask agent', () => { it('returns a string', async () => { const { result } = await runAgent({ agent: 'ask', payload: { question: 'hello' }, mock: true, }); expect(typeof result).toBe('string'); }); }); ``` ## Doctor ```sh fh doctor --tools # binary checks fh doctor --live --model openai/gpt-5.5 # one real provider round-trip ``` ## Dev server `fh dev --mock` starts the same routes the deployed Node target uses without provider credentials. Invoke a finite job at `/jobs/:name`: ```sh fh dev --mock --port 4000 curl -X POST -H 'Content-Type: application/json' \ -d '{"question":"What is Temporal?"}' \ http://localhost:4000/jobs/ask ``` ## Live integration tests Live tests are opt-in and skipped by default. Use them when validating real provider credentials and hosted resources: ```sh pnpm --filter @fabric-harness/connectors test # Daytona / E2B / Modal live suites skip unless enabled pnpm --filter @fabric-harness/azure test # Azure OpenAI / Foundry / ARM live suites skip unless enabled pnpm --filter @fabric-harness/databricks test # Databricks live suites skip unless enabled ``` See [Live tests](/docs/reference/live-tests) for the full environment variable matrix. ## Recipes from `examples/` The repo's `examples/` directory is the canonical reference for how to test each capability: - [`examples/hello-world`](https://github.com/) — basic metadata agents and real model invocation. - [`examples/with-tools`](https://github.com/) — built-in tools. - [`examples/with-skill`](https://github.com/) — skill loading and typed results. - [`examples/with-task`](https://github.com/) — durable task lifecycle and artifacts. - [`examples/with-approval`](https://github.com/) — approval-gated commands. - [`examples/with-docker`](https://github.com/) — Docker sandbox basics. - [`examples/with-temporal`](https://github.com/) — Temporal worker integration. - [`examples/with-config`](https://github.com/) — central config including SQLite session storage. - [`examples/with-postgres-store`](https://github.com/) — Postgres session/artifact storage. - [`examples/data-analyst`](https://github.com/) — Docker-backed CSV analysis with artifacts. - [`examples/issue-triage-ci`](https://github.com/) — controlled CI pilot for read-only GitHub issue triage. ## What to assert in tests For metadata agents, the most useful assertions are: 1. **Schema shape.** Output validates against the declared output schema. 2. **Tool/command scope.** No unexpected commands ran. 3. **Artifacts.** Expected artifacts were published with the right content type. 4. **Metrics.** Token / call counts stay within bounds for a given task. 5. **Idempotence.** Re-running the same prompt produces compatible output (when using the mock model or fixed seed). --- # Tools Canonical: https://harness.fabric.pro/docs/building/tools Built-in model-callable functions. Tools are functions the model can call during a session. Fabric Harness ships a built-in toolbelt that mirrors the proven coding-agent pattern. ## Built-in tools | Tool | Purpose | | --- | --- | | `read` | Read a file from the sandbox. | | `write` | Write a file to the sandbox. | | `edit` | Apply a localized edit (find/replace) to a file. | | `bash` | Execute a shell command in the sandbox. | | `grep` | Search file contents. | | `glob` | Match files by pattern. | | `task` | Spawn a child task. | All built-ins are **capability-aware**: `write` and `edit` honor any filesystem write scope declared on the session, `bash` honors the configured `commands` allowlist. ## Constructing the toolset ```ts import { createBuiltinTools } from '@fabric-harness/sdk'; const sandbox = await session.sandbox; const tools = createBuiltinTools(sandbox); await session.prompt('Find and fix the failing test', { tools }); ``` By default, `session.prompt()` uses the built-in tools. You typically only override this when you want to *limit* which tools are available or *augment* with custom ones. ## Custom tools > Both `defineTool` and `createBuiltinTools` are also exported from `@fabric-harness/sdk` — swap the import path when you want the minimal entrypoint. ```ts import { defineTool, schema } from '@fabric-harness/sdk'; const fetchIssue = defineTool({ name: 'fetch_issue', description: 'Fetch a GitHub issue by number.', input: schema.object({ number: schema.number() }), output: schema.object({ title: schema.string(), body: schema.string() }), run: async ({ input }) => { // ... return { title, body }; }, }); await session.prompt('Triage issue #42', { tools: [...tools, fetchIssue] }); ``` --- # Voice Canonical: https://harness.fabric.pro/docs/building/voice Bidirectional audio streaming for phone calls, browser mic/speaker, and kiosk audio. Realtime mode (OpenAI) + pipeline mode (Deepgram + ElevenLabs / Cartesia) behind one VoiceSession contract. `VoiceSession` is fabric-harness's surface for voice-capable LLMs. It streams audio bytes in both directions, surfaces text transcripts, and round-trips tool calls — same governance posture as text agents (cost telemetry, audit, approvals all apply). fabric-harness ships **two voice modes** behind the same `VoiceSession` contract: - **Realtime mode** — one model owns audio in + out + tools. OpenAI Realtime today; Anthropic / Gemini Live when GA. - **Pipeline mode** — `STT → LLM → TTS`. Compose Deepgram / Cartesia STT with any LLM and ElevenLabs / Cartesia TTS. Swap providers without changing the caller code. See [Voice Providers](/docs/building/voice-providers) for the provider matrix, pricing, and the picking heuristic. Telephony bridges (Twilio Media Streams, Vonage, Plivo) live in user-space; scaffold one with `fh add`. ## The contract ```ts interface VoiceSession { sendAudio(frame: Uint8Array): Promise; sendText(text: string): Promise; submitToolResult(input: { id: string; output: unknown }): Promise; cancelResponse(): Promise; events(): AsyncIterable; close(): Promise; } type VoiceEvent = | { type: 'session_open' } | { type: 'audio_delta'; audio: Uint8Array } | { type: 'text_delta'; delta: string; role: 'assistant' | 'user' } | { type: 'transcript'; text: string; role: 'assistant' | 'user' } | { type: 'tool_call'; id: string; name: string; input: unknown } | { type: 'response_done'; usage?: ModelUsage } | { type: 'error'; message: string }; ``` `audio_delta` carries raw PCM 16-bit (or `g711_ulaw` for telephony). `tool_call` flags an LLM tool invocation — your code runs the tool and calls `submitToolResult({ id, output })` to feed the result back. ## OpenAI Realtime ```ts import { OpenAIRealtimeVoiceProvider } from '@fabric-harness/sdk'; const provider = new OpenAIRealtimeVoiceProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o-realtime-preview', }); const voice = await provider.connect({ instructions: 'You are a friendly intake agent. One question at a time.', voice: 'alloy', audioFormat: 'pcm16', // or 'g711_ulaw' for Twilio tools: [submitFieldTool], turnDetection: 'server_vad', // OpenAI handles barge-in detection }); // Pump audio in (mic / phone) mic.on('data', (frame) => voice.sendAudio(frame)); // Pump audio out (speaker / phone) for await (const event of voice.events()) { if (event.type === 'audio_delta') speaker.write(event.audio); if (event.type === 'tool_call') { const output = await runTool(event.name, event.input); await voice.submitToolResult({ id: event.id, output }); } if (event.type === 'response_done') break; } ``` The provider uses the platform `WebSocket` (Node 22+ + browsers) — no `ws` dependency on the consumer side. ## Pipeline mode (ElevenLabs / Cartesia / Deepgram) When you want a non-OpenAI vendor combo — voice cloning, multilingual TTS, on-prem STT, or just lower per-minute cost — use `PipelineVoiceProvider`. It composes a `SttProvider`, a text `ModelProvider`, and a `TtsProvider` into the same `VoiceSession` contract. Swap any of the three without touching caller code. ```ts import { PipelineVoiceProvider, DeepgramSttProvider, ElevenLabsTtsProvider, AnthropicModelProvider, } from '@fabric-harness/sdk'; const provider = new PipelineVoiceProvider({ stt: new DeepgramSttProvider({ apiKey: process.env.DEEPGRAM_API_KEY!, model: 'nova-3' }), tts: new ElevenLabsTtsProvider({ apiKey: process.env.ELEVENLABS_API_KEY!, model: 'eleven_turbo_v2_5', defaultVoice: '21m00Tcm4TlvDq8ikWAM', // 'Rachel' }), llm: new AnthropicModelProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }), model: 'claude-haiku-4-5-20251001', }); const voice = await provider.connect({ instructions: 'Friendly intake agent. One question at a time.', tools: [submitFieldTool], }); // Same loop as realtime mode — events(), sendAudio(), submitToolResult(). ``` Conversation flow: 1. Caller pumps audio into `voice.sendAudio()`. 2. Deepgram emits final transcripts → pipeline forwards as the next user turn. 3. LLM generates the assistant reply (with tool calls if relevant). 4. Assistant text streams to ElevenLabs → audio bytes arrive as `audio_delta` events. 5. `response_done` fires with rolled-up usage (`inputTokens`, `outputTokens`, `sttSeconds`, `ttsCharacters`, `costUsd`). **Barge-in:** the STT VAD emits `speech_started` when the user interrupts; the pipeline aborts the in-flight LLM + TTS streams and returns to listening. Your UI should drain its audio buffer when `audio_delta` events stop arriving. **Single-vendor variant** (no ElevenLabs key needed): ```ts import { CartesiaSttProvider, CartesiaTtsProvider } from '@fabric-harness/sdk'; new PipelineVoiceProvider({ stt: new CartesiaSttProvider({ apiKey: process.env.CARTESIA_API_KEY! }), tts: new CartesiaTtsProvider({ apiKey: process.env.CARTESIA_API_KEY! }), llm: someLlmProvider, model: 'gpt-4.1-mini', }); ``` For the full matrix (when to pick which mode, latency benchmarks, cost comparisons), see [Voice Providers](/docs/building/voice-providers). ## Tool calls Tool execution is the *caller's* responsibility, not the voice session's. When `tool_call` lands: 1. Look up the tool by `name` in your registry. 2. Run it through whatever governance gates apply — approval policies, cost caps, rate limits — using the v1-era primitives unchanged. 3. Call `voice.submitToolResult({ id, output })`. This keeps the audio path lean and avoids reimplementing the loop machinery on the voice side. ## Cost Realtime audio is billed per audio token at materially higher rates than text. fabric-harness records `audioInputTokens` / `audioOutputTokens` on `usage` and rolls them into `costUsd` via the static price table: ``` $ fh metrics call-3a8b... Tokens: input=1200 output=400 total=1600 Audio: input=12345 output=6789 Cost: $0.234567 ``` Override the rates with `registerModelPrices` for negotiated contracts: ```ts import { registerModelPrices } from '@fabric-harness/sdk'; registerModelPrices([{ provider: 'openai', model: 'gpt-4o-realtime-preview', inputPerMTok: 4, outputPerMTok: 16, audioInputPerMTok: 80, audioOutputPerMTok: 160, effectiveAt: '2026-05-08', notes: 'Enterprise contract', }]); ``` ## Telephony bridges Phone calls? Don't add a Twilio dep to fabric-harness. Use `fh add` to scaffold a project-local bridge: ```sh fh add https://www.twilio.com/docs/voice/twiml/stream --category voice-telephony | claude fh add https://developer.vonage.com/voice/voice-api/code-snippets --category voice-telephony | cursor-agent ``` This emits the canonical telephony spec (`packages/sdk/connector-spec/voice-telephony.md`) with a header pointing at the provider's docs. The coding agent reads the docs, follows the spec, and produces a single file at `./connectors/-bridge.ts`. Twilio uses μ-law 8kHz natively — set `audioFormat: 'g711_ulaw'` and skip the resample for the lowest-latency path. ## Browser-direct voice via `fh server` `fh server` ships a `WS /sessions/:id/voice` upgrade handler (v1.11+). The server creates the OpenAI Realtime connection on behalf of the browser — provider API keys stay server-side. Tool calls relay through the agent's existing tool registry, so approval policies, cost caps, and rate limiters apply to voice tools just like text tools. ```ts import { connectFabricVoice } from '@fabric-harness/sdk'; const handle = connectFabricVoice({ url: `wss://app.example.com/sessions/${sessionId}/voice`, authToken, tenantId: 'acme', voice: 'alloy', instructions: 'Friendly intake agent. One question at a time.', onEvent: (event) => { if (event.type === 'audio') speaker.write(event.audio); if (event.type === 'tool_call') { // Optional: handle tool calls client-side. Default is server-side relay. } if (event.type === 'cost_limit') { console.warn('Hit cost ceiling', event); handle.close(); } }, onError: console.error, }); mic.on('frame', (pcm16) => handle.sendAudio(pcm16)); ``` Server requirements: - `OPENAI_API_KEY` env var (the bridge fails 1011 / `error` if missing). - `ws` peer dep installed (`pnpm add ws`). - Optional: `FABRIC_HARNESS_API_TOKEN`, `extractAuthToken`, `X-Fabric-Tenant` — same pipeline as the chat WS. Query params customize per-connection: `?model=gpt-4o-realtime-preview&voice=alloy&audioFormat=g711_ulaw&instructions=...`. ### Capture pipeline 1. `navigator.mediaDevices.getUserMedia({ audio: true })`. 2. Pipe through an `AudioWorklet` that resamples to PCM 16-bit 24kHz mono (or μ-law 8kHz for `audioFormat: 'g711_ulaw'`). 3. Forward each frame via `handle.sendAudio(buffer)`. ### Cost governance `VoiceConnectOptions.costBudget` (and the WS bridge's `costLimit` option) wires voice into the v1.4 cost-budget machinery. On every `response.done`, the tracker observes the call cost and emits a `cost_limit` event when a ceiling is crossed. With `onExceed: 'approve'`, your `requestCostLimitApproval` is called before the session continues. Pair with `tenantCostLimit()` for per-tenant per-period ceilings. ## See also - [Cost telemetry](/docs/building/model-providers#per-call-cost-telemetry) - [Connector catalog](/docs/building/connector-catalog) - [Session memory](/docs/building/session-memory) — useful for storing collected fields across calls --- # Voice Providers Canonical: https://harness.fabric.pro/docs/building/voice-providers Choose and compose realtime or pipeline voice providers with OpenAI, ElevenLabs, Cartesia, and Deepgram. Fabric Harness supports two voice modes: **realtime**, where one model owns audio input, audio output, and tools; and **pipeline**, where you choose separate STT, LLM, and TTS providers. ## Choose a voice architecture - Choose **OpenAI Realtime** when you want a single WebSocket connection with model-managed turn detection and tool calling. - Choose a **Deepgram + ElevenLabs pipeline** when independent STT, LLM, and TTS selection matters. - Choose a **Cartesia pipeline** when you want STT and TTS from one pipeline provider while retaining your preferred LLM. - Serve either mode through `WS /sessions/:id/voice` to keep provider credentials on the server. ## Mode comparison | Aspect | Realtime mode | Pipeline mode | |---|---|---| | Architecture | One WS, model handles audio in + out + tools | Three streams: STT → LLM → TTS | | Voice flexibility | Provider's voices only | Any TTS vendor — voice clones, emotion, accents | | Language support | Provider-bound | Determined by the selected STT and TTS providers | | Tool calling | Native to the model | Through the underlying LLM (Anthropic/OpenAI/Gemini) | | Barge-in | Server VAD | STT VAD + caller cancels TTS | | Useful for | Phone agents, intake bots, simpler audio orchestration | Multilingual, branded voice, vendor flexibility, provider-specific governance | ## Provider matrix | Provider | Role | Fabric integration | Consider when | |---|---|---|---| | OpenAI Realtime | Realtime end-to-end | `OpenAIRealtimeVoiceProvider` | You want one connection for audio, turn detection, responses, and tools. | | ElevenLabs | TTS | `ElevenLabsTtsProvider` | Voice selection and TTS controls are central requirements. | | Cartesia | TTS + STT | `CartesiaTtsProvider`, `CartesiaSttProvider` | You want one pipeline vendor for both speech directions. | | Deepgram | STT | `DeepgramSttProvider` | You need a dedicated streaming transcription provider with endpointing controls. | Provider models, languages, and prices change independently of Fabric Harness. Confirm the current provider offering, then use `registerModelPrices` to apply your public or negotiated rate card to cost telemetry. ## Realtime mode (OpenAI) ```ts import { OpenAIRealtimeVoiceProvider } from '@fabric-harness/sdk'; const provider = new OpenAIRealtimeVoiceProvider({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o-realtime-preview', }); const voice = await provider.connect({ instructions: 'You are a friendly intake agent.', voice: 'alloy', audioFormat: 'pcm16', // or 'g711_ulaw' for Twilio. tools: [submitFieldTool], turnDetection: 'server_vad', }); ``` The model owns the audio loop end-to-end. Tool calls relay through the existing `tool_call` / `submitToolResult` contract — same as pipeline mode. ## Pipeline mode (BYO STT + LLM + TTS) ```ts import { PipelineVoiceProvider, DeepgramSttProvider, ElevenLabsTtsProvider, AnthropicModelProvider, } from '@fabric-harness/sdk'; const provider = new PipelineVoiceProvider({ stt: new DeepgramSttProvider({ apiKey: process.env.DEEPGRAM_API_KEY!, model: 'nova-3', }), tts: new ElevenLabsTtsProvider({ apiKey: process.env.ELEVENLABS_API_KEY!, defaultVoice: '21m00Tcm4TlvDq8ikWAM', // 'Rachel' model: 'eleven_turbo_v2_5', }), llm: new AnthropicModelProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }), model: 'claude-haiku-4-5-20251001', }); const voice = await provider.connect({ instructions: 'You are a friendly intake agent. One question at a time.', audioFormat: 'pcm16', tools: [submitFieldTool], }); mic.on('frame', (pcm) => voice.sendAudio(pcm)); for await (const event of voice.events()) { if (event.type === 'audio_delta') speaker.write(event.audio); if (event.type === 'tool_call') { const output = await runTool(event.name, event.input); await voice.submitToolResult({ id: event.id, output }); } if (event.type === 'response_done') { // event.usage contains rolled-up tokens, sttSeconds, ttsCharacters, costUsd. } } ``` The contract is **identical** to realtime mode — `VoiceSession` events, tool calls, cost telemetry, `costBudget`, `cost_limit` events. Swap providers without rewriting your loop. ### Single-vendor variant (Cartesia) ```ts import { PipelineVoiceProvider, CartesiaSttProvider, CartesiaTtsProvider, } from '@fabric-harness/sdk'; const provider = new PipelineVoiceProvider({ stt: new CartesiaSttProvider({ apiKey: process.env.CARTESIA_API_KEY! }), tts: new CartesiaTtsProvider({ apiKey: process.env.CARTESIA_API_KEY! }), llm: someLlmProvider, model: 'claude-haiku-4-5-20251001', }); ``` ## Barge-in Pipeline mode wires barge-in through the STT VAD. When the user starts speaking while the agent is mid-utterance, the STT emits `speech_started`; the pipeline aborts the in-flight LLM call, cancels the TTS stream, and returns to listening. Your UI is responsible for stopping playback when `audio_delta` events stop arriving. Realtime mode handles this server-side via `turnDetection: 'server_vad'`. ## Cost telemetry Both modes feed the same telemetry surface. `response_done.usage` includes: ```ts { inputTokens: number; // LLM input outputTokens: number; // LLM output audioInputTokens?: number; // realtime mode only audioOutputTokens?: number;// realtime mode only sttSeconds?: number; // pipeline mode (Deepgram/Cartesia) ttsCharacters?: number; // pipeline mode (ElevenLabs/Cartesia) costUsd?: number; // rolled up via static price table } ``` Wire `costBudget` to enforce per-call / per-session / per-tenant ceilings — voice participates in the same v1.4 cost-budget machinery as text agents. ```ts import { CostBudgetTracker } from '@fabric-harness/sdk'; const budget = new CostBudgetTracker({ perCallUsd: 0.50, perSessionUsd: 5 }); await provider.connect({ costBudget: budget }); ``` ## Evaluate a voice stack Test candidate providers with representative audio before choosing a production stack. Measure: - time to first transcript and first synthesized audio; - transcription quality for your languages, accents, vocabulary, and audio channel; - interruption behavior under real network conditions; - voice consistency and pronunciation for your domain; - provider region, retention, residency, and audit controls; - end-to-end cost using your own traffic distribution and rate card. Because realtime and pipeline mode share the `VoiceSession` contract, you can run the same application loop against each candidate and compare session events and usage telemetry. ## When to bring your own provider Implement `TtsProvider`, `SttProvider`, or `VoiceProvider` directly and pass it into `PipelineVoiceProvider`. Reasons to build your own: - On-prem TTS (Riva, Coqui) for compliance. - Whisper-via-vLLM for cheap multilingual STT. - Translate-on-the-wire layers (e.g. STT in Spanish → MT → English LLM → TTS in Spanish). The interfaces (`TtsProvider`, `SttProvider`) are intentionally narrow — `synthesize(text) → AsyncIterable` and `open() → SttSession` are the only required methods. ## See also - [Voice](/docs/building/voice) — the `VoiceSession` contract and OpenAI Realtime usage. - [Cost telemetry](/docs/building/model-providers#per-call-cost-telemetry) — pricing and rate-card overrides. - [Connector catalog](/docs/building/connector-catalog) — telephony bridges (Twilio, Vonage) for phone audio. --- # CLI Overview Canonical: https://harness.fabric.pro/docs/cli One binary for run, build, dev, deploy, inspect, and replay. The Fabric Harness CLI is a single binary that drives the entire framework: discovering agents, running them locally, building deployment artifacts, starting the dev server, inspecting persisted sessions, and managing approvals, tasks, artifacts, and builds. ## Invocation The CLI is published as `fabric-harness` with `fh` as an exact alias: ```sh fabric-harness --help fh --help ``` For local framework development, run the built monorepo CLI directly: ```sh node packages/cli/dist/bin/fabric-harness.js --help pnpm fh --help ``` ## Synopsis ``` fabric-harness --help fabric-harness capabilities --json fabric-harness run [options] fabric-harness agents [--json] fabric-harness describe [--json] fabric-harness build [--target node|temporal-worker|docker|foundry-hosted-agent|cloudflare] [options] fabric-harness dev [--target node|cloudflare|temporal-worker] [--mock] [options] fabric-harness console [--url ] [--job --input | --agent --id --message ] fabric-harness docs fabric-harness doctor [--target node|temporal-worker] [--model provider/model] [--getting-started] [--tools] [--live] [--json] fabric-harness init [directory] [--dir ] [--model provider/model] [--template ] [--store memory|file|sqlite|postgres|redis] fabric-harness new [--force] fabric-harness sessions fabric-harness builds fabric-harness inspect fabric-harness logs [--events] fabric-harness checkpoints fabric-harness artifacts [--json] fabric-harness artifact get [--out ] fabric-harness metrics [--json] fabric-harness tasks [--json] fabric-harness task [--json] fabric-harness cancel-task [--actor ] [--reason ] fabric-harness compact [--keep ] [--summary ] fabric-harness replay fabric-harness approvals [--pending] [--state] fabric-harness approve [--actor ] [--reason ] fabric-harness reject [--actor ] [--reason ] fabric-harness verify-attestation fabric-harness verify-provenance fabric-harness test [path] [options] fabric-harness temporal-worker [--task-queue ] [--address ] [--env ] fabric-harness add [connector] [--print] ``` ## Command groups | Group | Commands | Page | | --- | --- | --- | | Run agents | `run` | [run](/docs/cli/run) | | Discover | `agents`, `describe` | [agents](/docs/cli/agents) | | Build | `build`, `builds`, `verify-attestation`, `verify-provenance` | [build](/docs/cli/build), [builds](/docs/cli/builds) | | Dev server | `dev` | [dev](/docs/cli/dev) | | Testing | `test` | [test](/docs/cli/test) | | Diagnostics | `doctor` | [doctor](/docs/cli/doctor) | | Integration compatibility | `capabilities --json` | [compatibility contract](/docs/cli/compatibility) | | Scaffolding | `init`, `new job`, `new agent` | [Fabric Harness in 5 minutes](/docs/getting-started/five-minutes) | | Interactive use | `console` | [console](/docs/cli/console) | | Documentation | `docs list`, `docs read`, `docs search` | [docs](/docs/cli/docs) | | Sessions | `sessions`, `inspect`, `logs`, `replay`, `metrics`, `compact` | [sessions](/docs/cli/sessions) | | Tasks | `tasks`, `task`, `cancel-task` | [tasks](/docs/cli/tasks) | | Approvals | `approvals`, `approve`, `reject` | [approvals](/docs/cli/approvals) | | Artifacts & checkpoints | `artifacts`, `artifact get`, `checkpoints` | [artifacts](/docs/cli/artifacts) | | Temporal | `temporal-worker` | [temporal-worker](/docs/cli/temporal-worker) | | Recipes | `add` | [add](/docs/cli/add) | ## Global options | Flag | Purpose | | --- | --- | | `-h`, `--help` | Print help text. | | `--env ` | Load `.env`-style variables before `run`/`dev`/`temporal-worker`. Repeatable; shell env wins. | ## Configuration defaults `.fabricharness/config.ts` may set: - `run.target`, `run.model`, `run.idPrefix`, - `temporal.address`, `temporal.taskQueue`, - `agent.model`. CLI flags always win over config. See [Configuration](/docs/getting-started/configuration). --- # fh add Canonical: https://harness.fabric.pro/docs/cli/add Browse, scaffold, or print ecosystem recipes. ``` fabric-harness add [kind] [name] [options] ``` `fh add` has two recipe modes: | Mode | Command shape | Result | | --- | --- | --- | | Managed recipe | `fh add ` or `fh add ` | Resolves dependencies, writes implementation/environment/test files, and prints a verification command. | | Connector guide | `fh add --print` | Prints Markdown for project-specific provider wiring. | The catalog labels every entry with its mode. Some connectors also have package helpers in `@fabric-harness/connectors`, `@fabric-harness/channels`, `@fabric-harness/azure`, or `@fabric-harness/databricks`. ## Examples ### List available recipes ```sh fh add fh add --json ``` ### Scaffold directly ```sh fh add channel slack fh add slack fh add channel teams fh add database postgres fh add sandbox e2b --dir ./agent-service fh add modal fh add policy safe-defaults ``` Scaffold kinds are `channel`, `database`, `sandbox`, `tooling`, `model-provider`, `skill`, and `policy`. Existing files are preserved unless `--force` is supplied. When `package.json` exists, Fabric detects pnpm, npm, Yarn, or Bun and installs missing declared dependencies using recipe-owned compatible version ranges. An incompatible existing range stops before files are written. Pass `--no-install` to update the manifest without running the package manager. Every recipe has a unique one-word alias. The explicit kind/name form remains useful in automation and makes ownership clear. Successful installation prints the package-manager-specific Vitest command for the generated contract test. Every generated TypeScript or Markdown file has a marker such as: ```ts // fabric-harness-recipe: channel/slack@1 ``` The marker lets [`fh update`](/docs/cli/update) distinguish an unchanged generated file from user customizations. Use `--dry-run` before applying a recipe in an existing project. ```sh fh add channel slack --dry-run fh add channel slack --dry-run --json ``` ### Print a connector recipe ```sh fh add daytona --print fh add e2b --print fh add github-mcp | codex fh add discord --install-deps --print ``` For sandbox connectors, the output demonstrates the `RemoteSandboxApi` / `SandboxEnv` adapter pattern. For MCP, KB, and data connectors, it demonstrates the appropriate public primitive: `connectMcpServer`, `FilesystemSource`, scoped `Command`, or `ToolDef`. ## Categories ```sh fh add https://provider.example/docs --category sandbox --print fh add https://provider.example/docs --category channel --print fh add https://provider.example/docs --category mcp --print fh add https://provider.example/docs --category kb --print fh add https://provider.example/docs --category data --print ``` | Category | Output shape | |---|---| | `channel` | Verified `Channel`, stable conversation key, durable dispatch, governed outbound tools | | `sandbox` | `SandboxEnv` / `SandboxFactory` | | `mcp` | `connectMcpServer()` wrapper | | `kb` | `FilesystemSource` | | `data` | `ToolDef`, `Command`, or MCP wrapper | | `database` | Bounded data tools; unified persistence bundles are documented for every maintained backend | | `tooling` | Telemetry, evaluation, or grading integration | ## Options | Flag | Description | | --- | --- | | `--json` | Print the catalog, or the direct-recipe change plan when kind/name are supplied. | | `--dir ` | Target directory for direct scaffold recipes. | | `--force` | Replace existing scaffold files. | | `--dry-run` | Print files, dependency resolutions, skips, and conflicts without writing. | | `--no-install` | Update dependency sections without running the detected package manager. | | `--install-deps` | Install dependencies declared by a connector guide. | | `--print` | Print a connector guide even when stdout is a terminal. | | `--category ` | Category for a provider documentation URL. | | `--pr` | Open a draft connector PR for the guide workflow. | ## Package helpers Prefer package helpers when available: - `@fabric-harness/connectors`: Daytona, E2B, Modal, Vercel, Kubernetes, generic remote sandbox, S3, Azure Blob. - `@fabric-harness/channels`: 17 maintained adapters spanning chat, developer tools, support, commerce, billing, knowledge, and email. - `@fabric-harness/databases`: Postgres, MySQL, MongoDB, Redis, and SQLite governed data tools. - `@fabric-harness/azure`: Azure OpenAI, Key Vault, Blob artifacts, Foundry Agent Service, Azure ARM tools. - `@fabric-harness/databricks`: SQL, Jobs, notebooks, Unity Catalog, MLflow, workspace source. ## See also - [Connector catalog](/docs/building/connector-catalog) - [fh update](/docs/cli/update) - [Ecosystem catalog](/docs/ecosystem) - [Sandbox connectors](/docs/building/sandbox-connectors) - [Capability matrix](/docs/reference/capability-matrix) — which connectors are first-class today. --- # fh agents and fh describe Canonical: https://harness.fabric.pro/docs/cli/agents List and inspect workspace agents. ## `fh agents` ``` fabric-harness agents [--json] ``` List every agent under `.fabricharness/agents/`. The default output is human-readable; `--json` emits a machine-readable summary. ```sh fh agents fh agents --json ``` ## `fh describe` ``` fabric-harness describe [--json] ``` Show metadata for a single agent: declared model, target default, triggers, and the input/output schema. ```sh fh describe ask fh describe ask --json ``` For agents declared with `agent({...})`, `describe` shows: - name and description, - input schema, rendered as JSON Schema, - output schema, - declared model, - triggers (`webhook`, etc.), - run target default, - examples (if `examples: [...]` was provided). Plain default-exported functions are rejected; every agent should use `agent({...})` so `describe` can expose the contract. ## Why this exists `describe` is the contract for tooling. Anything that runs Fabric Harness agents — CI, dev servers, deploy pipelines — can use `--json` to discover schemas without parsing TypeScript. --- # Approvals Canonical: https://harness.fabric.pro/docs/cli/approvals Resolve human approval requests from the CLI. Agents that gate destructive actions can call `session.approval.request({ reason, risk, timeoutMs })` to pause until a human responds. The CLI is the simplest way to resolve those requests during development and CI. ## `fh approvals` ``` fabric-harness approvals [--pending] [--state] ``` List approval requests for a session. `--pending` filters to unresolved requests; `--state` includes the request's current state machine (timeouts, history of actions). ## `fh approve` ``` fabric-harness approve [--actor ] [--reason ] ``` Mark an approval as approved. The associated session resumes as soon as the running agent (or worker) observes the resolution. ```sh fh approve ask-1f4f... appr-7 --actor preetham --reason "Cleared by SRE" ``` ## `fh reject` ``` fabric-harness reject [--actor ] [--reason ] ``` Mark an approval as denied. The agent typically aborts the gated action and returns an actionable failure. ## See also - [Building agents → Approvals](/docs/building/approvals) - [Policies and approvals](/docs/reference/policies-approvals) --- # Artifacts and Checkpoints Canonical: https://harness.fabric.pro/docs/cli/artifacts List, fetch, and inspect session-bound artifacts and checkpoints. **Artifacts** are files (Markdown, JSON, CSV, images, ...) that an agent publishes during a session via `session.artifacts.publish(path, options?)`. **Checkpoints** are explicit named save points. ## `fh artifacts` ``` fabric-harness artifacts [--json] ``` List artifacts for a session. The default output shows id, name, content type, byte size, and creation time. ## `fh artifact get` ``` fabric-harness artifact get [--out ] ``` Fetch a single artifact. If `--out` is omitted, the artifact prints to stdout (use redirection for binary content). If both an id and a name match, the id wins. ```sh fh artifact get ask-1f4f... report.md --out ./reports/report.md ``` ## `fh checkpoints` ``` fabric-harness checkpoints ``` List checkpoints for a session. Each entry includes label, created time, and (when available) a sandbox snapshot reference. ## See also - [Building agents → Artifacts](/docs/building/artifacts) - [Artifacts and observability](/docs/reference/build-manifest) --- # fh build Canonical: https://harness.fabric.pro/docs/cli/build Compile the workspace and emit a deployment manifest. ``` fabric-harness build [--target ] [options] ``` Compiles `.fabricharness/jobs/` and `.fabricharness/agents/`, then emits a deployment artifact under `.fabricharness/build//` with a schema-v2 `manifest.json`. ## Targets | Target | Output | | --- | --- | | `node` (default) | Shared v2 Node HTTP server with finite `/jobs/:name` and persistent `/agents/:name/:id` routes. | | `temporal-worker` | A worker entrypoint that registers Fabric workflows + activities against a Temporal task queue. | | `docker` | `Dockerfile` + Node bundle ready for `docker build`. | | `cloudflare` | Worker entrypoint, Durable Object session store, Sandbox container binding, `wrangler.jsonc`. | | `foundry-hosted-agent` | `Dockerfile`, `azure.yaml`, `infra/main.bicep`, `foundry-agent.yaml`, server bundle. | | `databricks-app` | Databricks App bundle, `app.yaml`, Lakebase-aware server, and Asset Bundle config. | | `databricks-serving` | MLflow pyfunc proxy and Model Serving deployment assets. | ## Options | Flag | Description | | --- | --- | | `--out ` | Override the output directory (default `.fabricharness/build/`). | | `--env ` | Load `.env`-style variables before building (e.g. `--env .env.production`). Auto-loads `.env`/`.env.local`; shell env wins. | | `--no-clean` | Skip cleaning the output directory before emit. | | `--sbom` | Emit a CycloneDX SBOM via Syft when available. | | `--sbom-required` | Fail if Syft is missing. | | `--provenance` | Emit `provenance.json` for the build artifact. | | `--attestation` | Emit `attestation.intoto.jsonl` with a manifest digest subject. | | `--sign-provenance` | Sign `provenance.json` via `cosign sign-blob`. Implies `--provenance`. | | `--signing-key ` | cosign key path or env reference (default `env://COSIGN_PRIVATE_KEY`). | | `--docker-build` | Run `docker build` after emitting `--target docker`. | | `--docker-push` | Run `docker push` after `--docker-build`. | | `--docker-tag ` | Tag for the built/pushed image. | | `--image-sbom` | Emit an image SBOM via Syft. | | `--image-sbom-required` | Fail if image SBOM cannot be produced. | ## Examples ### Node server artifact ```sh fh build --target node node .fabricharness/build/node/dist/server.mjs ``` ### Docker image with SBOM ```sh fh build --target docker --docker-build --docker-tag myorg/agents:latest --sbom --image-sbom ``` ### Cloudflare scaffold ```sh fh build --target cloudflare cd .fabricharness/build/cloudflare npm install @cloudflare/sandbox @fabric-harness/cloudflare @fabric-harness/sdk npx wrangler dev ``` ### Foundry Hosted Agent scaffold ```sh fh build --target foundry-hosted-agent cd .fabricharness/build/foundry-hosted-agent azd up ``` ### Signed provenance + attestation ```sh export COSIGN_PRIVATE_KEY=$(cat cosign.key) fh build --target node --provenance --sign-provenance --attestation ``` ## What's in `manifest.json` - The Fabric Harness version and target, - separate `jobs` and persistent `agents` collections with schemas, models, and triggers, - declared sandbox backend(s), - session-store backend, if configured, - digest of the artifact, - optional provenance/attestation metadata. The CLI exposes manifests through `fh builds`. A Node server can read workspace-local artifacts at `GET /builds/:target/manifest`; Cloudflare exposes its embedded manifest at `GET /manifest`. See also: [Build and run artifacts](/docs/deployment/build-artifacts), [Build manifest](/docs/reference/build-manifest), [`fh builds`](/docs/cli/builds), [`fh verify-attestation`](/docs/cli/builds#verify). --- # Builds and Verification Canonical: https://harness.fabric.pro/docs/cli/builds List emitted build manifests and verify provenance/attestations. ## `fh builds` ``` fabric-harness builds ``` List every build manifest emitted under `.fabricharness/build/`. Output includes target, output dir, agent count, manifest digest, and timestamps. ## `fh verify-attestation` ``` fabric-harness verify-attestation ``` Verify the in-toto attestation produced by `fh build --attestation`. The argument can be a build directory (the CLI finds `attestation.intoto.jsonl`) or a direct path to the attestation file. ```sh fh verify-attestation .fabricharness/build/node fh verify-attestation .fabricharness/build/node/attestation.intoto.jsonl ``` ## `fh verify-provenance` ``` fabric-harness verify-provenance ``` Verify the SLSA-style provenance produced by `fh build --provenance`. Optionally signed with cosign via `--sign-provenance`. ```sh fh verify-provenance .fabricharness/build/node ``` If a `provenance.sig` is present, the CLI invokes `cosign verify-blob` with the configured public key. Otherwise it validates structure and digests only. ## See also - [`fh build`](/docs/cli/build) — emit artifacts and provenance. - [Build manifest](/docs/reference/build-manifest) — manifest schema. - [Security hardening](/docs/reference/security-hardening) — when to require provenance. --- # Compatibility Contract Canonical: https://harness.fabric.pro/docs/cli/compatibility Negotiate Fabric Harness CLI features safely from Desktop, CI, and other automation. Fabric Harness exposes a machine-readable contract so an integrating application can verify behavior before it runs a command. Use capability negotiation together with a supported version range. A version check alone cannot prove that a build includes the commands, targets, and protocol versions your application needs. ```bash fh capabilities --json ``` The command writes JSON to stdout and does not inspect a workspace or read credentials: ```json { "schemaVersion": 1, "product": "fabric-harness", "cliVersion": "2.2.2", "protocolVersion": 1, "buildManifestVersion": 2, "commands": ["init", "run", "build", "deploy", "doctor", "capabilities"], "buildTargets": ["node", "temporal-worker", "docker", "databricks-app", "databricks-serving"], "runtimes": ["inline", "temporal"], "features": ["cli.preview-deploy", "databricks.app-build", "databricks.ai-gateway"] } ``` The arrays can gain values in a compatible release. Consumers should require the values they use and ignore values they do not recognize. A changed `schemaVersion`, `protocolVersion`, or `buildManifestVersion` requires an explicit compatibility decision. ```mermaid flowchart LR A[Desktop or automation] --> B[Resolve fh executable] B --> C[Check supported semver range] C --> D[Run fh capabilities --json] D --> E{Required protocol and features present?} E -->|Yes| F[Run init, build, deploy, or run] E -->|No| G[Stop with an upgrade or compatibility error] ``` ## Consumer example ```ts import { execFileSync } from 'node:child_process'; const capabilities = JSON.parse( execFileSync('fh', ['capabilities', '--json'], { encoding: 'utf8' }), ); const required = [ 'cli.preview-deploy', 'databricks.app-build', 'databricks.app-deploy', 'databricks.ai-gateway', ]; if (capabilities.protocolVersion !== 1) { throw new Error(`Unsupported Harness protocol ${capabilities.protocolVersion}`); } const missing = required.filter((feature) => !capabilities.features.includes(feature)); if (missing.length > 0) { throw new Error(`Harness is missing: ${missing.join(', ')}`); } ``` ## Release testing Fabric Desktop tests both its minimum certified Harness release and the current npm `latest`. Harness contributors can test an unpublished CLI against the adjacent Desktop checkout: ```bash pnpm test:desktop-compat ``` The command builds and packs `@fabric-harness/cli`, installs that tarball in isolation, then drives Desktop's real Databricks bridge through scaffold, mock run, build validation, and deploy preview for every bundled template. It also verifies the capability contract and scans generated files for leaked credentials. Keep application templates on exact package versions. Upgrade those pins only after the packed-artifact compatibility gate passes; this prevents a new transitive CLI release from changing an existing scaffold without review. --- # console Canonical: https://harness.fabric.pro/docs/cli/console Interact with jobs, persistent agents, streams, tool calls, and approvals from a terminal. `fh console` is a lightweight terminal client for the public Fabric HTTP protocol. It does not start a second runtime or bypass server policy. Authentication, tenant isolation, durable admission, offsets, tool events, and approval resolution behave the same as other clients. Start the server in one terminal: ```sh FABRIC_HARNESS_API_TOKEN=local-token fh dev --mock ``` Then open the interactive console: ```sh FABRIC_HARNESS_API_TOKEN=local-token fh console ``` The console reads `/admin/agents`, lets you choose a finite job or persistent agent, and prompts for an instance id when needed. Persistent messages are admitted asynchronously. The console follows the conversation from the returned offset and prints assistant messages, tool calls, tool results, approval events, errors, and terminal settlement. ## One-shot jobs ```sh fh console --job report --input '{"topic":"weekly usage"}' ``` Input must be JSON. The job uses the authenticated `/jobs/:name` route and prints the complete result envelope. ## One-shot persistent messages ```sh fh console \ --agent support \ --id customer-42 \ --session billing \ --message "Where is invoice 1007?" ``` Use `--url` for a deployed server, `--tenant` for explicit operator tenant selection, and `--token-env` when the bearer token is stored under a different environment variable: ```sh PROD_AGENT_TOKEN=... fh console \ --url https://agents.example.com \ --token-env PROD_AGENT_TOKEN \ --tenant acme \ --agent support --id customer-42 --message "Retry the export" ``` Tokens are read from the environment rather than command arguments, so they do not appear in shell history or process listings. ## Interactive commands | Command | Effect | | --- | --- | | `:approvals ` | List approval requests for a session. | | `:approve [reason]` | Approve through the server's RBAC route. | | `:reject [reason]` | Reject through the same route. | | `:quit` | Close the terminal client. | The identity used to resolve an approval comes from the authenticated server principal. Text typed after the approval id is a reason, not an actor override. --- # fh dev Canonical: https://harness.fabric.pro/docs/cli/dev Start a local HTTP/SSE dev server for the workspace. ``` fabric-harness dev [--target node|cloudflare|temporal-worker] [options] ``` `fh dev` boots the selected target's development server and watches both `.fabricharness/jobs/` and `.fabricharness/agents/`. The Node target uses the same v2 server as Node-derived builds. Cloudflare uses Wrangler for finite jobs and Durable Object-backed persistent agents. ## Routes - `GET /health` - `GET /ready` - `POST /jobs/:name` — invoke a finite job with its JSON input. - `POST /agents/:name/:id` — durably admit a persistent message and return `202`. - `GET /agents/:name/:id/stream?offset=...` — tail persistent conversation records. - `GET /builds/:target/manifest` — read a build manifest still under the workspace. Cloudflare also exposes `GET /manifest`, but does not currently expose persistent-agent routes. ## Options | Flag | Description | | --- | --- | | `--target ` | Default `node`. | | `--env ` | Load `.env`-style variables. Repeatable; shell env wins. | | `--mock` | Use the deterministic mock model provider for every request. | | `--host ` | Listen address (default `127.0.0.1`). | | `--port ` | Listen port (default `3030`). | | `--auth-token-env ` | Require `Authorization: Bearer $$VAR` for invocations. | | `--max-body-bytes ` | Reject request bodies larger than `n`. | | `--rate-limit-window-ms ` | Rate-limit window in ms. | | `--rate-limit-max ` | Rate-limit max requests per window. | ## Example ```sh fh dev --mock --port 4000 --auth-token-env FABRIC_DEV_TOKEN ``` ```sh curl -X POST \ -H "Authorization: Bearer $FABRIC_DEV_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"question":"What is Temporal?"}' \ http://localhost:4000/jobs/ask ``` ## When to use which - **`fh run`** — one-shot invocations, scripts, CI smoke tests. - **`fh dev`** — local development with hot reload, finite job webhook testing, and durable persistent-agent streams. See [HTTP Server](/docs/reference/http-server) for the complete Node route table. --- # docs Canonical: https://harness.fabric.pro/docs/cli/docs List, read, and search Fabric Harness documentation as Markdown from the CLI. The docs commands consume the published `llms.txt`, `llms-full.txt`, and per-page Markdown routes. They work in a source checkout or any project with the CLI installed. ## List routes ```sh fh docs list fh docs list --query databricks fh docs list --json ``` Each entry includes its stable slug, title, and description. Duplicate priority/index entries are removed. ## Read a page ```sh fh docs read databricks/quickstart fh docs read /docs/operating/auth fh docs read https://harness.fabric.pro/docs/building/http-applications ``` Output is the page's Markdown source, suitable for a terminal pager or an LLM context file: ```sh fh docs read databricks/integrations | less fh docs read reference/api > /tmp/fabric-api.md ``` ## Search the corpus ```sh fh docs search unity catalog lineage fh docs search durable approval --limit 5 fh docs search lakebase credential --json ``` Search ranks title and description matches before full-corpus occurrences and prints a nearby text snippet. Use `--base-url` to test a preview or self-hosted documentation worker with the same routes. --- # fh doctor Canonical: https://harness.fabric.pro/docs/cli/doctor Diagnose workspace, tools, and live model connectivity. ``` fabric-harness doctor [--target node|temporal-worker] [--model provider/model] [--getting-started] [--tools] [--live] [--json] ``` `fh doctor` validates that your workspace is set up correctly and (optionally) that a real model can be invoked. ## Options | Flag | Description | | --- | --- | | `--target ` | Validate the chosen run target. | | `--model ` | Probe a specific model (combine with `--live`). | | `--getting-started` | Check Node version, ESM package setup, Fabric dependencies, npm scripts, and `.fabricharness/agents` discovery. | | `--tools` | Enumerate built-in tools and required binaries (`docker`, `gh`, etc.). | | `--live` | Make a small live request against the model provider. | | `--json` | Emit a machine-readable report. | ## Examples ### Local readiness ```sh fh doctor --getting-started --tools ``` ### Live model check ```sh cp .env.example .env.local # edit .env.local and set OPENAI_API_KEY=... fh doctor --live --model openai/gpt-5.5 ``` ### Temporal target ```sh fh doctor --target temporal-worker ``` The doctor checks Temporal connectivity using config/env defaults — make sure `FABRIC_TEMPORAL_ADDRESS` and your task queue are correct. ## What it checks - Workspace root resolution. - Agents discovered. - Node/package/ESM/dependency setup (when `--getting-started`). - Built-in tool schemas and binaries on PATH (when `--tools`). - Model provider configuration. - Optional live model round-trip when `--live`. - Optional Temporal connectivity for `--target temporal-worker`. --- # fh run Canonical: https://harness.fabric.pro/docs/cli/run Execute a workspace agent against a local Node runtime or a Temporal worker. ``` fabric-harness run [options] ``` Runs the named agent. The CLI loads the agent module, validates the input payload, calls `run({ init, input, payload })`, and validates the output. ## Options | Flag | Description | | --- | --- | | `--id ` | Run/agent identifier. If omitted, a session id is generated using `${idPrefix}-${uuid}`. | | `--target ` | Where the agent runs. Defaults to `node`. `temporal-worker` switches `--runtime` to `temporal`. | | `--runtime ` | Equivalent lower-level flag. Usually set via `--target`. | | `--model ` | Override the agent's default model, e.g. `openai/gpt-5.5`. | | `--cwd ` | Default sandbox/session working directory for this run. Relative paths stay scoped inside the sandbox workspace. | | `--prompt ` | Direct prompt text for `--runtime temporal` mode. | | `--payload ''` | Pass an inline JSON payload. | | `--payload-file ` | Read JSON payload from a file. | | `--stdin` | Read JSON payload from stdin. | | `--set key=value` | Set a payload field. May be repeated. | | `-- ` | Shortcut for setting a payload field, e.g. `--question "..."`. | | `--env ` | Load `.env`-style variables. Repeatable; shell env wins. | ## Payload precedence When more than one source is given, fields merge in this order (later wins): ``` --payload → --payload-file → --stdin → --set / -- / key=value ``` ## Examples ### Inline JSON ```sh fh run ask --payload '{"question":"What is Temporal?"}' ``` ### Field shortcut ```sh fh run ask --question "What is Temporal?" fh run ask question="What is Temporal?" fh run ask --set question="What is Temporal?" ``` ### Working directory ```sh fh run code --cwd /workspace/project --prompt "Run tests and summarize failures" fh run code --cwd packages/core --payload '{"prompt":"Inspect this package"}' ``` `--cwd` is a sandbox cwd, not a host directory switch. Use it to make file/shell tools default to a repository subdirectory. ### From a file or stdin ```sh fh run ask --payload-file input.json echo '{"question":"hi"}' | fh run ask --stdin ``` ### Real model Put provider keys once in a repo/workspace `.env.local`; Fabric Harness auto-loads it and shell env still wins. ```sh cp .env.example .env.local # edit .env.local and set OPENAI_API_KEY=... fh run ask --model openai/gpt-5.5 --question "What is Temporal?" ``` Use explicit `--env ` only for test/CI overrides. ### Temporal worker target ```sh # In a separate terminal: fh temporal-worker # Then: fh run ask --target temporal-worker --id ask-001 --prompt "What is Temporal?" ``` ## What happens during `run` 1. Resolve workspace root by walking up to find `.fabricharness/`. 2. Load `.fabricharness/config.ts` and merge with env and CLI flags. 3. Resolve agent path, target, runtime, model, id, and sandbox cwd. 4. Apply env model provider (e.g. set provider credentials). 5. Validate input payload against the agent's input schema (metadata agents only). 6. Call the agent's `run({ init, input, payload })`. 7. Validate output against the output schema (metadata agents only). 8. Persist session and emit a session id. ## See also - [`fh agents` / `fh describe`](/docs/cli/agents) — discover what's runnable. - [Sessions](/docs/cli/sessions) — inspect what just happened. - [Configuration](/docs/getting-started/configuration) — defaults and precedence. --- # Sessions, Inspect, Logs, Replay, Metrics, Compact Canonical: https://harness.fabric.pro/docs/cli/sessions Inspect what happened during a run. Every `fh run` and every dev-server invocation persists a session under `.fabricharness/sessions/` (or your configured store). The CLI ships several commands for reading those sessions. ## `fh sessions` ``` fabric-harness sessions ``` Lists persisted sessions. Output includes session id, agent name, last update time, and entry counts. ## `fh inspect` ``` fabric-harness inspect ``` Shows the full structured session: prompts, assistant messages, tool calls, tool results, shell commands, task starts/ends, approvals, compactions, checkpoints, and artifacts. ## `fh logs` ``` fabric-harness logs [--events] ``` Prints a readable timeline. Use `--events` to include the lower-level event stream (`text_delta`, `tool_start`, `tool_end`, etc.). ## `fh metrics` ``` fabric-harness metrics [--json] [--breakdown] ``` Aggregate token, tool, shell, and artifact metrics for the session. Anthropic prompt-cache tokens (`Cache: read=N write=N`) are surfaced when present. When the SDK was able to estimate USD cost from the static price table (or the provider supplied it directly), a `Cost: $0.XXXX` line is printed. Pass `--breakdown` to print per-`provider/model` cost line items: ```text Cost: $0.012450 openai/gpt-4o: $0.011200 anthropic/claude-haiku-4-5-20251001: $0.001250 ``` The cost catalog is a static table shipped in the SDK — override or extend it with `registerModelPrices()` when you have custom-rate contracts. Real-time billing reconciliation is out of scope; use vendor invoices for billing. ## `fh export-audit` ``` fabric-harness export-audit [--format jsonl|csv] [--output |-] ``` Streams every entry in the session log out as one row per line — JSONL by default, CSV via `--format csv`. Each row carries normalized columns: timestamp, type, entryId, parentId, tool, command, model, provider, durationMs, inputTokens, outputTokens, cachedInputTokens, cacheWriteTokens, costUsd, approvalId, audience, eventType, idempotencyKey, errorMessage. Pipe straight into your SIEM / data warehouse: ```sh fh export-audit ask-1f4f... --format jsonl | jq -c 'select(.type == "model_attempt")' fh export-audit ask-1f4f... --format csv --output audit.csv ``` `--output -` writes to stdout (default when `--output` is omitted). Big sessions stream line-by-line — no full-session buffering. ## `fh replay` ``` fabric-harness replay ``` Read-only view of the session's *active path* (post-compaction) and the model context that would be sent on the next turn. Useful for debugging compactions and prompt drift. ## `fh compact` ``` fabric-harness compact [--keep ] [--summary ] ``` Append a compaction entry to a persisted session. `--keep ` keeps the last *n* entries verbatim, `--summary ` provides an explicit summary instead of running the model. ## Example session lifecycle ```sh fh run ask --question "What is Temporal?" # Session id: ask-1f4f... fh inspect ask-1f4f... fh logs ask-1f4f... fh metrics ask-1f4f... fh replay ask-1f4f... fh compact ask-1f4f... --keep 2 --summary "User asked about Temporal." ``` --- # Tasks Canonical: https://harness.fabric.pro/docs/cli/tasks Inspect, cancel, and manage durable child tasks. A **task** in Fabric Harness is a child or delegated agent run, typically started via `session.task()` from agent code. Task state is persisted alongside the parent session and exposed via the CLI. ## `fh tasks` ``` fabric-harness tasks [--json] ``` List durable tasks for a session: id, status, start/end time, parent prompt, and result (if completed). ## `fh task` ``` fabric-harness task [--json] ``` Show the status, checkpoints, and metadata of a single task. ## `fh cancel-task` ``` fabric-harness cancel-task [--actor ] [--reason ] ``` Append a cancellation marker so the running task observes the cancel and exits at its next checkpoint. ```sh fh cancel-task ask-1f4f... task-2 --actor preetham --reason "Replaced by manual fix" ``` ## `fh checkpoints` ``` fabric-harness checkpoints ``` List checkpoints inside a session. Checkpoints are explicit save points created via `session.checkpoint.create({ label })`. When the sandbox supports snapshots, a checkpoint may include a sandbox snapshot reference. ## See also - [Building agents → Tasks](/docs/building/tasks) - [Building agents → Approvals](/docs/building/approvals) --- # fh temporal-worker Canonical: https://harness.fabric.pro/docs/cli/temporal-worker Run a local Temporal worker that executes Fabric Harness activities. ``` fabric-harness temporal-worker [--task-queue ] [--address ] [--env ] ``` Starts a long-running Temporal worker that registers Fabric Harness workflows and activities (model calls, tool execution, sandbox commands, result validation, compaction, child tasks). Pair this with `fh run --target temporal-worker` to drive durable agent sessions. ## Options | Flag | Description | | --- | --- | | `--task-queue ` | Defaults to `FABRIC_TEMPORAL_TASK_QUEUE`, then `config.temporal.taskQueue`, then `fabric-harness`. | | `--address ` | Defaults to `FABRIC_TEMPORAL_ADDRESS`, then `config.temporal.address`, then `localhost:7233`. | | `--env ` | Explicit env-file override. Repo/workspace `.env` and `.env.local` are auto-loaded; shell env wins. | Additional behavior comes from `.fabricharness/config.ts` and env: - `FABRIC_TEMPORAL_NAMESPACE` / `config.temporal.namespace` - `config.temporal.apiKey` / `config.temporal.apiKeyEnv` - `config.temporal.tls` ## Example ```sh # Terminal 1: a local Temporal dev server (e.g. via `temporal server start-dev`) temporal server start-dev # Terminal 2: Fabric worker; provider keys can live in repo-level .env.local fh temporal-worker --task-queue fabric-harness # Terminal 3: drive an agent fh run ask --target temporal-worker --id ask-001 --prompt "What is Temporal?" ``` ## What it does on startup 1. Resolves workspace root and config. 2. Creates a `FileSessionStore` rooted at the workspace. 3. Creates an empty sandbox plus the built-in tool set. 4. Resolves the model + provider from env/config (`applyEnvModelProvider`). 5. Registers Fabric activities (`createLocalTemporalActivities`). 6. Starts the Temporal worker on the resolved address + task queue. 7. Listens for `SIGINT`/`SIGTERM` and shuts the worker down cleanly. ## See also - [Deployment → Temporal worker](/docs/deployment/temporal-worker) - [Configuration](/docs/getting-started/configuration) --- # fh test Canonical: https://harness.fabric.pro/docs/cli/test Discover and run eval suites. ``` fabric-harness test [path] [options] ``` `fh test` discovers `*.eval.ts` files, imports them as eval suites, runs each case through the suite's runner, scores the outputs, and prints a summary. It exits `0` when every suite passes and `1` when any suite fails. ## Options | Flag | Description | | --- | --- | | `--pattern ` | Glob pattern for eval files (default `**/*.eval.ts`). | | `--suite ` | Run only a specific suite by name. | | `--scorer ` | Filter reporting to a specific scorer. | | `--json` | Output results as JSON. | | `--threshold ` | Override the pass threshold for all suites. | ## Example eval file ```ts import { defineEvalSuite, exactMatchScorer } from '@fabric-harness/evals'; export default defineEvalSuite({ name: 'hello-world', cases: [ { id: 'greeting', input: 'hello', expected: 'HELLO' }, ], runner: ({ case: evalCase }) => String(evalCase.input).toUpperCase(), scorers: [exactMatchScorer()], }); ``` ## Examples Run all eval suites in the current directory: ```sh fh test ``` Run eval suites in a specific directory: ```sh fh test examples/hello-world ``` Run only a specific suite: ```sh fh test --suite hello-world ``` Output results as JSON: ```sh fh test --json ``` Override the pass threshold: ```sh fh test --threshold 0.9 ``` ## Exit codes | Code | Meaning | | --- | --- | | `0` | All suites passed. | | `1` | One or more suites failed, or an error occurred. | ## See also - [Eval library reference](/docs/reference/eval-library) - [CLI overview](/docs/cli) --- # fh update Canonical: https://harness.fabric.pro/docs/cli/update Safely update versioned managed recipes while preserving project changes. ```sh fh update [options] fh update [options] ``` `fh update` plans every managed-file and dependency change before it writes. Recipe markers identify the installed version. Compatible project dependency ranges remain unchanged; incompatible ranges and modified managed files are reported as conflicts. ## Review an update ```sh fh update channel slack --dry-run fh update slack --dry-run fh update channel slack --json ``` Human output is intended for terminal review. JSON output contains the recipe version, package manager, file actions, dependency actions, upgrade notes, and conflicts without embedding generated file contents. Both outputs include the package-manager-specific verification command. ## Apply an update ```sh fh update channel slack fh update database postgres --no-install ``` Fabric checks the entire plan first. It then installs missing compatible dependencies and applies managed file/environment changes as one file transaction. If a file write fails, previously written files are restored. An unmarked file can be adopted automatically only when it exactly matches the previous generated content. A marked file with user changes is never replaced automatically: ```txt Recipe channel:slack has conflicts: - .fabricharness/channels/slack.ts: user-modified managed file at version 1 ``` Review and merge the new recipe manually, or use `--force` when replacement is intentional. ## Options | Flag | Description | | --- | --- | | `--dir ` | Project containing the managed recipe. | | `--dry-run` | Print the complete plan without writing or installing. | | `--json` | Emit a machine-readable plan. | | `--no-install` | Update `package.json` without running the package manager. | | `--force` | Replace conflicting managed files after review. | ## Guarantees - Unknown or incompatible dependency ranges fail before recipe files change. - A recipe never silently downgrades a newer managed version. - Existing user files without a matching recipe marker are preserved. - Missing files from an installed recipe can be recreated. - Running an update twice is idempotent. See [Connector recipes](/docs/reference/recipes) for recipe ownership and authoring conventions. --- # Databricks development with Fabric Harness Canonical: https://harness.fabric.pro/docs/databricks/development Turn Databricks AI and data services into durable, governed TypeScript applications with a local-first workflow. Fabric Harness is the application runtime around Databricks AI and data services. It does not replace Unity Catalog, Spark, Lakeflow, MLflow, Jobs, SQL Warehouses, or Model Serving. It gives developers one TypeScript workflow for composing those services into agents that can run locally, deploy to Databricks Apps, preserve identity, enforce policy, persist state, and produce operational evidence. ## Where Fabric fits ```mermaid flowchart LR subgraph Author[Developer workflow] CODE[TypeScript job or agent] MOCK[Local mock and tests] BUILD[Fabric build] end subgraph Runtime[Fabric application runtime] APP[Databricks App] LOOP[Session and model loop] POLICY[Policy and approvals] AUDIT[Lineage, cost, and telemetry] end subgraph Platform[Databricks services] GATEWAY[Unity AI Gateway] DATA[SQL and Unity Catalog] RAG[Vector Search and Genie] COMPUTE[Jobs and Lakeflow] STATE[Lakebase and Volumes] MLFLOW[MLflow and system tables] end CODE --> MOCK MOCK --> BUILD BUILD --> APP APP --> LOOP LOOP --> POLICY POLICY --> GATEWAY POLICY --> DATA POLICY --> RAG POLICY --> COMPUTE LOOP --> STATE LOOP --> AUDIT AUDIT --> MLFLOW classDef author fill:#f4f4f5,stroke:#71717a,color:#18181b classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef control fill:#fef3c7,stroke:#d97706,color:#422006 classDef dbx fill:#dcfce7,stroke:#16a34a,color:#052e16 class CODE,MOCK,BUILD author class APP,LOOP,AUDIT fabric class POLICY control class GATEWAY,DATA,RAG,COMPUTE,STATE,MLFLOW dbx ``` Databricks remains authoritative for data permissions, compute, model access, and platform operations. Fabric owns the agent lifecycle around those services: admission, sessions, tools, approvals, durable state, retries, typed results, audit correlation, and deployment artifacts. ## Start locally, connect later Create a Databricks-oriented project without requiring workspace credentials: ```sh npx @fabric-harness/cli init \ --template databricks \ --dir analytics-agent cd analytics-agent npm install ``` The template creates a finite analytics job, role, skill, governed SQL policy, Databricks App configuration, environment sample, and certification manifest. ```sh fh agents fh describe databricks-analyst fh run databricks-analyst \ --question 'Describe main.sales.orders' \ --mock ``` Mock mode exercises discovery, input validation, tool assembly, the model loop, and HTTP routing. It deliberately does not simulate Unity Catalog grants or claim that a workspace API succeeded. When the local behavior is ready, configure a PAT for single-user development or OAuth M2M for a production-like service principal: ```dotenv title=".env.local" DATABRICKS_HOST=https://adb-1234567890123456.7.azuredatabricks.net DATABRICKS_CLIENT_ID=00000000-0000-0000-0000-000000000000 DATABRICKS_CLIENT_SECRET=resolve-from-your-secret-manager DATABRICKS_WAREHOUSE_ID=0123456789abcdef DATABRICKS_MODEL=system.ai.gpt-oss-20b DATABRICKS_INFERENCE_MODE=auto ``` Run the same definition without `--mock` to exercise the real workspace. See [Local naming and authentication](/docs/databricks/local-naming-auth) for PAT, OAuth M2M, App identity, OBO, tenant, and deployment-profile behavior. ## Use AI Gateway without custom HTTP plumbing Use a discovered `system.ai.*` service as the model: ```ts title=".fabricharness/jobs/databricks-analyst.ts" import { defineDatabricksAgent } from '@fabric-harness/databricks'; import { schema } from '@fabric-harness/sdk'; import policy from '../policies/databricks.js'; export default defineDatabricksAgent({ name: 'databricks-analyst', description: 'Answer governed questions using workspace data.', input: schema.object({ question: schema.string() }), output: schema.string(), model: 'system.ai.gpt-oss-20b', tools: ['sql', 'tables', 'table-info'], triggers: { manual: true, webhook: true }, sandbox: 'empty', policy, }); ``` Fabric automatically routes `system.ai.*` through the workspace Unity AI Gateway path. Custom endpoint names route through Model Serving. The Databricks provider adds: - OAuth token acquisition, caching, and early refresh; - request tags for submission, attempt, tenant, and agent identifiers; - retry-safe requests and redacted provider errors; - model usage and cost correlation; - a common provider contract for local tests and deployed runtimes. Discover enabled services in the target workspace instead of assuming a model is available. The [integration map](/docs/databricks/integrations#inference-names-and-urls) documents automatic routing and explicit overrides. ## Compose the governed Databricks stack Use `databricks()` when an application needs several Databricks services under one identity: ```ts import { databricks } from '@fabric-harness/databricks'; import { init } from '@fabric-harness/sdk'; const dbx = databricks({ host: process.env.DATABRICKS_HOST!, principal: { kind: 'service-principal', host: process.env.DATABRICKS_HOST!, clientId: process.env.DATABRICKS_CLIENT_ID!, clientSecret: process.env.DATABRICKS_CLIENT_SECRET!, }, model: 'system.ai.gpt-oss-20b', warehouseId: process.env.DATABRICKS_WAREHOUSE_ID, vectorSearch: { index: 'main.knowledge.docs_index', textColumn: 'chunk', idColumn: 'id', }, genie: { spaceId: process.env.DATABRICKS_GENIE_SPACE_ID! }, lakeflow: true, consumption: true, governance: { catalogs: ['main'], stewardAudience: 'data-steward', }, }); const runtime = await init({ modelProvider: dbx.modelProvider, tools: dbx.tools, policy: dbx.policy, store: dbx.store, }); ``` One principal is threaded through model calls, REST APIs, SQL, and optional Lakebase credential exchange. Fabric policy can narrow access or require approval, while Unity Catalog and Databricks resource ACLs make the final authorization decision. ## Add only the workloads you need Managed recipes add project-local wiring, compatible dependencies, environment stubs, and verification commands: ```sh fh add databricks core fh add databricks sql fh add vector-search fh add lakebase fh add lakeflow fh add jobs ``` Recipes are dependency-aware and refuse incompatible ranges rather than silently replacing them. Use `fh add --dry-run` to inspect changes and `fh update` to update managed recipe files. ## Workload patterns | Workload | Databricks services | What Fabric adds | | --- | --- | --- | | Governed lakehouse analyst | AI Gateway, SQL Warehouse, Unity Catalog | Typed input/output, SQL policy, approvals, tenant identity, audit lineage | | RAG application | Vector Search, AI Gateway, MLflow 3 | Retrieval orchestration, validated citations, evaluation export, release quality gates | | Persistent copilot | Databricks Apps, Lakebase, UC Volumes | Addressable instances, conversation streams, durable submissions, attachments, deletion | | Data operations agent | Jobs, notebooks, Lakeflow | Idempotent admission, status/output collection, approval gates, durable receipts | | BI assistant | Genie, SQL Warehouse, Unity Catalog | Governed tool composition, session context, principal and tenant propagation | | Feature-aware agent | Feature Serving, Model Serving | Low-latency feature lookup as a governed tool with model usage correlation | | Model Serving integration | AI Gateway or custom endpoints | One provider API, OAuth refresh, request tags, retries, usage and cost attribution | | ChatAgent interoperability | MLflow ChatAgent proxy, Databricks Apps | Registered Python proxy in Model Serving with the TypeScript runtime hosted in an App | ## Databricks-native RAG quality Fabric's deterministic RAG chain follows a preprocess, retrieve, augment, generate, and validate flow. Vector Search performs retrieval, AI Gateway performs generation, and MLflow 3 performs managed evaluation. ```mermaid flowchart LR QUESTION[Golden-set question] --> RETRIEVE[Vector Search retrieval] RETRIEVE --> GENERATE[AI Gateway generation] GENERATE --> TRACE[MLflow trace] TRACE --> JUDGES[Managed judges] JUDGES --> REL[Answer relevance] JUDGES --> RREL[Retrieval relevance] JUDGES --> GROUND[Groundedness] JUDGES --> SUFF[Sufficiency] JUDGES --> CORRECT[Correctness] REL --> GATE{All meet threshold?} RREL --> GATE GROUND --> GATE SUFF --> GATE CORRECT --> GATE GATE -->|Yes| PASS[Release evidence] GATE -->|No| BLOCK[Block release] classDef source fill:#f4f4f5,stroke:#71717a,color:#18181b classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef dbx fill:#dcfce7,stroke:#16a34a,color:#052e16 classDef decision fill:#fef3c7,stroke:#d97706,color:#422006 classDef deny fill:#fee2e2,stroke:#dc2626,color:#450a0a class QUESTION source class RETRIEVE,GENERATE,TRACE,JUDGES dbx class REL,RREL,GROUND,SUFF,CORRECT,PASS fabric class GATE decision class BLOCK deny ``` The certification fixture uses managed relevance, retrieval relevance, groundedness, sufficiency, and correctness judges with a configurable threshold. A project should replace the small certification fixture with its own domain questions, expected facts, retrieval expectations, insufficient-context cases, and adversarial inputs. See [RAG on Databricks](/docs/databricks/rag). ## Durable Apps instead of stateless demos `databricks-app` bundles the Node server and agent definitions for Databricks Apps. Optional Lakebase persistence stores sessions, submissions, and conversation streams. Unity Catalog Volumes store governed attachments. ```sh fh build --target databricks-app fh deploy --preview --target databricks-app --profile analytics-dev fh deploy --target databricks-app --profile analytics-dev ``` The generated artifact contains `app.yaml`, `databricks.yml`, the self-contained server bundle, roles, skills, definitions, and build manifest. Runtime credentials come from App identity and resource bindings, not the developer's deployment profile. For configuration-preserving recovery, redeploy the generated bundle with the same variables. A bare App start can omit values injected during deployment. The [Databricks App tutorial](/docs/deployment/databricks-app) documents the certified deployment and recovery procedure. ## Governance and operations are runtime behavior Fabric controls are evaluated around every agent operation rather than described only in a prompt: - capability and catalog policies constrain available actions; - sensitive SQL, pipeline, and mutation tools can require durable approval; - Fabric principals and tenants propagate into submissions and request tags; - Unity Catalog remains authoritative for tables, schemas, volumes, rows, and columns; - Lakebase telemetry joins actors, submissions, governed objects, outcomes, and estimated cost; - System Tables reconcile delayed actual usage with tenant and agent budgets; - cascade deletion removes sessions, submissions, streams, and attachments; - MLflow and OpenTelemetry expose traces and operational evidence. This is most useful when an agent moves from a notebook experiment to a shared application with multiple users, governed data, long-running work, or production operating requirements. ## When to use Fabric Harness Use Fabric when the application needs several of these together: - local TypeScript development and Databricks App deployment; - model calls plus SQL, retrieval, Jobs, Lakeflow, Genie, or Feature Serving; - persistent agents or durable workflow state; - user, service-principal, tenant, and OBO identity propagation; - approval, policy, audit, deletion, or cost enforcement; - repeatable workspace certification and deployment evidence. A direct Databricks SDK or notebook is usually simpler for a one-off query, a standalone Spark transformation, or a single model request with no agent lifecycle. Fabric is valuable when those calls need to become a governed application. ## Production validation Fabric ships local contracts and a protected workspace certification runner. Before approving a deployment, validate the actual cloud, region, workspace, identity, resources, data, and workload: 1. Verify OAuth M2M or App identity and every required resource permission. 2. Prove one allowed and one deliberately denied Unity Catalog operation. 3. Exercise AI Gateway, SQL, retrieval, Jobs, Lakeflow, and other enabled services. 4. Deploy the App, persist work, redeploy, and verify recovery and cascade deletion. 5. Run the project's MLflow evaluation dataset and enforce quality thresholds. 6. Verify lineage and System Tables cost reconciliation for real tenant tags. 7. Test concurrency, rate limits, long-running work, and expected failure modes. 8. Exercise OBO login, expiry, refresh, and user-specific grants when OBO is enabled. 9. Retain redacted certification, compatibility, recovery, and conformance evidence. Certification records are environment-specific. Do not infer that a passing Azure workspace also certifies an untested AWS/GCP workspace, region, preview feature, or production corpus. Use the [workspace compatibility matrix](/docs/databricks/compatibility) and [submission readiness guide](/docs/databricks/submission-readiness) for the complete evidence path. ## Next steps - [Create the reference project](/docs/databricks/quickstart) - [Understand local names and identities](/docs/databricks/local-naming-auth) - [Choose Databricks integrations](/docs/databricks/integrations) - [Build and evaluate RAG](/docs/databricks/rag) - [Deploy a Databricks App](/docs/deployment/databricks-app) - [Apply enterprise controls](/docs/databricks/enterprise) --- # Fabric Desktop for Databricks projects Canonical: https://harness.fabric.pro/docs/databricks/fabric-desktop Create, run, inspect, and deploy Fabric Harness Databricks projects from Fabric Desktop, CLI, or server mode. Fabric Desktop provides a guided interface over the same Fabric Harness project files and `fh` CLI used by terminal and CI workflows. A project binding stores non-secret workspace defaults, while credentials remain in Desktop's source credential manager and Databricks authorization remains the authority for every workspace operation. ```mermaid flowchart LR RECIPE[Choose a recipe] --> AUTH[Select an OAuth source] AUTH --> DISCOVER[Discover required resources] DISCOVER --> PROJECT[Create a bound project] PROJECT --> RUN[Mock or confirmed live run] PROJECT --> BROWSE[Browse workspace resources] PROJECT --> BUILD[Build and preview] BUILD --> CONFIRM{Confirm host and target} CONFIRM --> DEPLOY[Deploy Databricks App] DEPLOY --> OPEN[Open App URL] classDef local fill:#f4f4f5,stroke:#71717a,color:#18181b classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef dbx fill:#dcfce7,stroke:#16a34a,color:#052e16 class RECIPE,AUTH,DISCOVER local class PROJECT,RUN,BUILD,CONFIRM fabric class BROWSE,DEPLOY,OPEN dbx ``` ## Enable the project workflow During release certification, start Desktop with the project workflow enabled: ```sh FABRIC_FEATURE_DATABRICKS_PROJECTS=1 bun run electron:dev ``` The feature flag is a Desktop release control. It does not change Harness CLI behavior or weaken the authentication requirements of a generated App. ## Create a bound project 1. Create a project and choose a Databricks recipe. 2. Select one workspace identity. For local development, choose `databricks-oauth` and **Databricks CLI OAuth**. Desktop reads profiles with `databricks auth profiles --output json`, derives the workspace URL from the selected profile, and refreshes tokens every 30 minutes. 3. Use the inline **Configure** control when the identity is not yet verified. Desktop calls the workspace identity API before displaying **Verified**. OAuth-app U2M, service-principal M2M, and an explicit PAT source remain available for environments that require them. 4. Choose **Discover workspace resources** and select the resources requested by the recipe. 5. Create the project. Desktop scaffolds the recipe and writes the selected non-secret identifiers into `.env.example` and the project binding. | Recipe | Resources selected during setup | | --- | --- | | Governed Analytics Copilot | SQL Warehouse, catalog, schema, optional Genie space | | Lakebase Stateful RAG Agent | catalog, schema, Model Serving endpoint, Vector Search index | | Data Engineering Agent | SQL Warehouse, catalog, schema, Lakeflow pipeline, Databricks Job | Optional APIs are discovered independently. For example, missing permission to list Apps does not prevent a SQL-only project from selecting its Warehouse. When a required API cannot be listed, the wizard accepts the stable resource identifier directly. SQL Warehouse, Unity Catalog, and Vector Search tools reuse this verified workspace identity. They do not ask for or store duplicate tokens. Changing the identity updates the linked capability sources to the same normalized workspace origin. The project binding resembles the following. It contains no access token, client secret, or App API token: ```json { "host": "https://adb-1234567890123456.7.azuredatabricks.net", "authSourceId": "databricks-oauth", "catalog": "main", "schema": "analytics", "warehouseId": "0123456789abcdef", "profile": "DEFAULT", "resources": { "servingEndpoint": "databricks-gpt-oss-120b", "vectorIndex": "main.analytics.product_docs" } } ``` Every project session receives this binding as structured context. Installed Databricks skills are attached according to the binding: SQL and Unity Catalog, Genie, Vector Search RAG, Lakeflow, Asset Bundle authoring, and cost optimization. A required source is enabled only when that source exists, is authenticated, and is usable. ## Browse the workspace Open the project's **Workspace** view to inspect: - Unity Catalog catalogs, schemas, and tables; - SQL Warehouses and Model Serving endpoints; - Databricks Apps and Jobs; - Genie spaces and Vector Search indexes; - Lakeflow pipelines. Catalog nodes load schemas and tables lazily. Selecting a table inserts its fully qualified `catalog.schema.table` name into the latest session for that project. Other resources insert their stable name or identifier. Resource groups load independently so a permission failure is isolated to the affected group. ## Run, build, and deploy Open **Deploy** on the project: 1. **Mock run** exercises the selected job without workspace side effects. 2. **Live run** shows a confirmation containing the workspace host and bundle target. The approval authorizes that operation only. 3. **Build** creates the Harness deployment artifact. 4. **Preview** validates the deployment plan without deploying the App. 5. **Deploy** requires target-and-host confirmation and streams redacted output. 6. **Cancel** terminates the active Harness subprocess. 7. **Open app** uses the URL captured in deployment history. Desktop serializes operations per project, parses `databricks.yml` structurally, temporarily selects the requested Asset Bundle target, and restores the source file even when the command fails or is cancelled. Deployment history is stored with the project. The bridge validates the installed `fh` version and capability contract before invoking it. The terminal remains intentionally fail-closed. A non-interactive live run still requires the CI or operator to set the live gate explicitly: ```sh export FABRIC_DATABRICKS_TEST=1 fh run analyst --payload '{"question":"Summarize main.analytics.orders"}' ``` An interactive Desktop confirmation does not set this global environment variable and does not change subsequent terminal commands. ## AI Gateway attribution and guardrails Project settings accept cost attribution tags: ```text team=analytics, cost_center=finance, environment=prod ``` They also accept an AI Gateway guardrail JSON object: ```json { "input": { "pii": true, "safety": true }, "output": { "safety": true, "toxicity": true } } ``` For a Databricks Mosaic AI session, Desktop passes these values only to that project's isolated model subprocess. The provider adds `X-Cost-Attribution-Tags` and `X-Guardrail-Config` to AI Gateway requests. The project context indicator shows when attribution and guardrails are active. ## Desktop, CLI, and server mode ```mermaid flowchart TB UI[Fabric Desktop UI] --> RPC[Typed project RPC] WEB[Desktop server mode] --> RPC RPC --> BRIDGE[Databricks bridge] CLI[Terminal or CI] --> FH[fh CLI] BRIDGE --> FH FH --> FILES[Same project and databricks.yml] FH --> DBX[Databricks APIs and Apps] classDef client fill:#f4f4f5,stroke:#71717a,color:#18181b classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef dbx fill:#dcfce7,stroke:#16a34a,color:#052e16 class UI,WEB,CLI client class RPC,BRIDGE,FH,FILES fabric class DBX dbx ``` Recipe lookup uses the shared bundled-assets resolver, so the same gallery and scaffolding work in the Electron application and headless Desktop server. The generated Databricks developer resource bundle contains the versioned skill pack and can be imported into another Desktop workspace without copying credentials. For the authentication boundaries and naming rules, continue with [Local naming and authentication](/docs/databricks/local-naming-auth). For the release protocol, see the [CLI compatibility contract](/docs/cli/compatibility). --- # Local naming and authentication Canonical: https://harness.fabric.pro/docs/databricks/local-naming-auth Understand Fabric job, agent, App, tenant, and Databricks principal names while developing locally. Local development involves two independent authentication boundaries and several intentionally separate names. Keeping them separate prevents a local API token from being mistaken for a Databricks credential, or a model endpoint name from becoming an agent route. ## The complete local request ```mermaid flowchart LR subgraph Caller[Local caller] CLI[fh run] HTTP[HTTP client] end subgraph Fabric[Local Fabric runtime] INGRESS[Fabric ingress authentication] PRINCIPAL[Fabric principal and tenant] ROUTE[Job or agent route] SESSION[Session and policy] end subgraph DatabricksAuth[Databricks authentication] PAT[Developer PAT] M2M[OAuth M2M service principal] OBO[On-behalf-of user] end subgraph Databricks[Databricks workspace] MODEL[AI Gateway or Model Serving] DATA[SQL, Unity Catalog, Vector Search] STATE[Lakebase and Volumes] end CLI -->|trusted local process| ROUTE HTTP -->|Bearer token or local default| INGRESS INGRESS --> PRINCIPAL PRINCIPAL --> ROUTE ROUTE --> SESSION SESSION --> PAT SESSION --> M2M SESSION --> OBO PAT --> MODEL PAT --> DATA M2M --> MODEL M2M --> DATA M2M --> STATE OBO --> DATA classDef local fill:#f4f4f5,stroke:#71717a,color:#18181b classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef identity fill:#fef3c7,stroke:#d97706,color:#422006 classDef dbx fill:#dcfce7,stroke:#16a34a,color:#052e16 class CLI,HTTP local class INGRESS,PRINCIPAL,ROUTE,SESSION fabric class PAT,M2M,OBO identity class MODEL,DATA,STATE dbx ``` The Fabric principal answers **who may invoke this local runtime and tenant**. The Databricks principal answers **who performs the model, data, and state operation in the workspace**. They can be the same organizational identity, but they use different credentials and are configured independently. ## Naming model ```mermaid flowchart TB PROJECT[Project metadata.name] --> BUNDLE[Asset Bundle name] PREFIX[run.idPrefix] --> APP[Databricks App resource name] JOBFILE[.fabricharness/jobs/report.ts] --> JOBNAME[Declared job name or report] AGENTFILE[.fabricharness/agents/support.ts] --> AGENTNAME[Persistent route support] AGENTNAME --> INSTANCE[Caller instance customer-42] INSTANCE --> NAMEDSESSION[Named session default] MODELENV[DATABRICKS_MODEL] --> MODELSERVICE[AI Gateway model service] classDef source fill:#f4f4f5,stroke:#71717a,color:#18181b classDef fabric fill:#dbeafe,stroke:#2563eb,color:#172554 classDef dbx fill:#dcfce7,stroke:#16a34a,color:#052e16 class PROJECT,PREFIX,JOBFILE,AGENTFILE,MODELENV source class BUNDLE,APP,JOBNAME,AGENTNAME,INSTANCE,NAMEDSESSION fabric class MODELSERVICE dbx ``` | Name | Source | Example | Used for | | --- | --- | --- | --- | | Bundle name | `metadata.name` | `analytics-agents` | Databricks Asset Bundle identity | | App name | `run.idPrefix` | `analytics-app` | Databricks App resource | | Job name | Definition `name`, then relative file path | `daily-report` | CLI and `/jobs/:name` | | Persistent agent name | Relative path under `agents/` | `support` | `/agents/:name/:instanceId` | | Instance ID | Supplied by the caller | `customer-42` | Addressable persistent agent instance | | Session name | Supplied by the caller; default `default` | `incident-104` | Conversation within an instance | | Databricks model | `DATABRICKS_MODEL` or definition model | `system.ai.gpt-oss-20b` | AI Gateway or serving endpoint selection | Configure the project and App names in `.fabricharness/config.ts`: ```ts title=".fabricharness/config.ts" export default { metadata: { name: 'analytics-agents' }, run: { idPrefix: 'analytics-app', target: 'node', }, }; ``` `metadata.name` and `run.idPrefix` are sanitized for Databricks resource naming. They do not rename jobs, persistent agents, Unity Catalog objects, or model endpoints. ### Finite job names For finite jobs, a declared name is authoritative: ```ts title=".fabricharness/jobs/report.ts" import { agent, schema } from '@fabric-harness/sdk'; export default agent({ name: 'daily-report', input: schema.object({ accountId: schema.string() }), output: schema.string(), model: 'databricks/system.ai.gpt-oss-20b', triggers: { manual: true, webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); return session.prompt(`Summarize account ${input.accountId}.`); }, }); ``` ```sh fh run daily-report --account-id account-42 ``` ```http POST /jobs/daily-report ``` If `name` is omitted, `jobs/report.ts` becomes `report`. Nested paths remain namespaced: `jobs/operations/daily.ts` becomes `operations/daily`. ### Persistent agent names Persistent routes derive from the relative path under `.fabricharness/agents/`: ```ts title=".fabricharness/agents/support.ts" import { createAgent } from '@fabric-harness/sdk'; export default createAgent(({ id }) => ({ model: 'databricks/system.ai.gpt-oss-20b', instructions: `Support account ${id} using governed workspace data.`, triggers: { webhook: true }, })); ``` The route below addresses agent `support`, instance `customer-42`, session `incident-104`: ```sh curl -X POST \ 'http://localhost:3000/agents/support/customer-42?wait=false' \ -H 'content-type: application/json' \ -d '{"message":"Investigate the failed pipeline.","session":"incident-104"}' ``` Fabric persists that identity as the tuple `(support, customer-42, incident-104)`. The instance and session names are application identifiers, not Databricks users or Unity Catalog principals. ## Fabric authentication while local Direct CLI execution does not cross an HTTP authentication boundary: ```sh fh run daily-report --account-id account-42 --mock ``` `fh dev` permits requests without a token in local mode and records them as the synthetic `local-development` service principal. This is intended only for loopback development. To exercise the authenticated HTTP path locally, set an API token before starting the server: ```sh export FABRIC_HARNESS_API_TOKEN="$(openssl rand -hex 32)" fh dev ``` ```sh curl -X POST http://localhost:3000/jobs/daily-report \ -H "authorization: Bearer $FABRIC_HARNESS_API_TOKEN" \ -H 'content-type: application/json' \ -d '{"accountId":"account-42"}' ``` When `FABRIC_ENV=production` or `NODE_ENV=production`, the server fails closed. With no API token or custom `authenticate` hook, protected requests receive `401`. Query-string tokens exist for limited transport compatibility; use the `Authorization` header for normal clients so tokens do not enter URLs or access logs. For OIDC, role, permission, and tenant-aware ingress, use a structured server authenticator. See [Authentication and RBAC](/docs/operating/auth) for JWT validation and Databricks Apps presets. ### Tenant selection A structured Fabric principal can be bound to one tenant or an allowlist: ```ts { id: 'analyst-123', kind: 'user', tenantIds: ['customer-42', 'customer-84'], permissions: ['agent:invoke', 'session:read'], roles: ['analyst'], } ``` The caller selects an allowed tenant with `X-Fabric-Tenant`: ```sh curl -X POST http://localhost:3000/jobs/daily-report \ -H "authorization: Bearer $FABRIC_HARNESS_API_TOKEN" \ -H 'x-fabric-tenant: customer-42' \ -H 'content-type: application/json' \ -d '{"accountId":"account-42"}' ``` A single-tenant principal binds implicitly. A multi-tenant allowlist requires an explicit selection, and selecting a tenant outside the allowlist returns `403`. ## Databricks authentication while local Fabric runtime credentials come from environment variables or an explicit `DatabricksPrincipal`. For local development, the recommended path uses the same Databricks CLI OAuth profile for runtime tokens and deployment. Production Databricks Apps continue to use their app service principal and optional on-behalf-of user token. ### Databricks CLI OAuth for local development Authenticate once with the Databricks CLI: ```sh databricks auth login \ --host https://adb-1234567890123456.7.azuredatabricks.net \ --profile fabric-harness ``` Then select that profile for Harness runtime token resolution: ```dotenv title=".env.local" DATABRICKS_HOST=https://adb-1234567890123456.7.azuredatabricks.net DATABRICKS_CONFIG_PROFILE=fabric-harness DATABRICKS_AUTH_MODE=cli DATABRICKS_MODEL=system.ai.gpt-oss-20b ``` Harness invokes `databricks auth token --host ... --profile ... --output json`, caches only the returned access token, and refreshes it every 30 minutes with `--force-refresh`. If the CLI cache is stale, it attempts `databricks auth login --no-browser` and retries. A pasted AI Gateway URL is normalized to the workspace origin before management or authentication URLs are constructed. Explicit wiring is also available: ```ts import { databricksIdentity } from '@fabric-harness/databricks'; const token = databricksIdentity({ kind: 'cli-profile', host: process.env.DATABRICKS_HOST!, profile: 'fabric-harness', }); ``` ### PAT for one developer Use a personal access token for a controlled, single-user development session: ```dotenv title=".env.local" DATABRICKS_HOST=https://adb-1234567890123456.7.azuredatabricks.net DATABRICKS_TOKEN=dapi... DATABRICKS_WAREHOUSE_ID=0123456789abcdef DATABRICKS_MODEL=system.ai.gpt-oss-20b DATABRICKS_INFERENCE_MODE=auto ``` The process acts with the PAT owner's Databricks permissions. Do not use a developer PAT as an App or shared production identity. PAT lookup is explicit; `DATABRICKS_BEARER` is also accepted for headless compatibility with pre-fetched Databricks tokens. ### OAuth M2M for production-like development Use a service principal to reproduce CI and App-style authorization locally: ```dotenv title=".env.local" DATABRICKS_HOST=https://adb-1234567890123456.7.azuredatabricks.net DATABRICKS_CLIENT_ID=00000000-0000-0000-0000-000000000000 DATABRICKS_CLIENT_SECRET=resolve-from-your-secret-manager DATABRICKS_WAREHOUSE_ID=0123456789abcdef DATABRICKS_MODEL=system.ai.gpt-oss-20b DATABRICKS_INFERENCE_MODE=auto ``` Fabric exchanges the client credentials at the workspace OIDC endpoint, caches the access token, refreshes before expiry, and deduplicates concurrent refreshes. If `DATABRICKS_TOKEN` and OAuth credentials are both present, the PAT wins; unset `DATABRICKS_TOKEN` when validating M2M behavior. Run a live request after loading the environment: ```sh fh run databricks-analyst \ --question 'Describe main.sales.orders' ``` ### Explicit principal wiring Use an explicit principal when one process needs more than one Databricks identity: ```ts import { databricks } from '@fabric-harness/databricks'; const dbx = databricks({ host: process.env.DATABRICKS_HOST!, principal: { kind: 'service-principal', host: process.env.DATABRICKS_HOST!, clientId: process.env.DATABRICKS_CLIENT_ID!, clientSecret: process.env.DATABRICKS_CLIENT_SECRET!, }, model: 'system.ai.gpt-oss-20b', warehouseId: process.env.DATABRICKS_WAREHOUSE_ID, governance: { catalogs: ['main'] }, }); ``` The token resolver is awaited for each Databricks operation. Raw credentials are not added to model messages, tool inputs, lineage labels, or logs. ### OBO for a signed-in user On-behalf-of authentication preserves a user's Unity Catalog grants: ```ts const principal = { kind: 'on-behalf-of' as const, userToken: async () => currentUserAccessToken(), label: 'signed-in-analyst', }; ``` Fabric does not manufacture or persist an OBO token. Databricks Apps performs user consent and token refresh, then forwards the current user access token on each request. `databricksApp().onBehalfOf()` maps that request-scoped token to the Fabric actor and Unity Catalog principal. For a protected diagnostic route, `inspectDatabricksAppUserAuthorization()` validates the forwarded token against the workspace current-user API and returns only identity plus issued/expiry metadata. It never returns the token or copies workspace error bodies. The reference App exposes this at `GET /certification/obo` for lifecycle certification. ```mermaid sequenceDiagram autonumber actor Analyst participant Ingress as Fabric ingress participant Runtime as Fabric runtime participant Provider as Databricks token provider participant UC as Unity Catalog Analyst->>Ingress: Request with signed user identity Ingress->>Ingress: Validate issuer, audience, expiry, signature Ingress->>Runtime: Fabric user principal and tenant Runtime->>Provider: Resolve short-lived user token Provider-->>Runtime: OBO access token Runtime->>UC: Query as signed-in analyst UC-->>Runtime: User-specific result or denial Runtime-->>Analyst: Audited response ``` One interactive CLI authorization can certify both initial login and refresh. The prepare phase uses the selected U2M profile and stores only non-secret lifetime metadata. After the first one-hour token expires, verify asks the CLI for a refreshed token, calls the App again, and requires the same user, a newer issue time, and a later expiry: ```sh databricks auth login --host "$DATABRICKS_HOST" --profile fabric-harness DATABRICKS_APP_URL="$DATABRICKS_APP_URL" \ DATABRICKS_PROFILE=fabric-harness \ node scripts/certify-databricks-obo-lifecycle.mjs prepare # Run after the expiry printed by prepare. DATABRICKS_APP_URL="$DATABRICKS_APP_URL" \ DATABRICKS_PROFILE=fabric-harness \ node scripts/certify-databricks-obo-lifecycle.mjs verify ``` ## Runtime credentials versus deployment profiles `DATABRICKS_TOKEN` or the OAuth environment variables authenticate calls made by the running agent. A Databricks CLI profile authenticates build deployment commands: ```sh databricks auth login --host "$DATABRICKS_HOST" --profile analytics-dev fh build --target databricks-app fh deploy --target databricks-app --profile analytics-dev ``` Passing `--profile` does not change the identity used later by the deployed App. Databricks Apps supplies its service-principal identity at runtime; configure App resource permissions and secret references separately. ## Local verification checklist 1. Run `fh agents` and confirm the job and persistent-agent route names. 2. Run with `--mock` before adding workspace credentials. 3. Set only one Databricks credential mode at a time. 4. Use OAuth M2M to reproduce unattended CI permissions. 5. Set `FABRIC_HARNESS_API_TOKEN` to test authenticated local HTTP calls. 6. Test one allowed and one denied Unity Catalog object. 7. Verify the selected Fabric tenant appears in audit, lineage, and cost attribution. 8. Use `fh deploy --preview --target databricks-app --profile analytics-dev` before deploying. Continue with the [Databricks quickstart](/docs/databricks/quickstart) to scaffold an App or [Enterprise Databricks controls](/docs/databricks/enterprise) to configure OIDC, policy, durability, lineage, and cost enforcement. --- # RAG on Databricks Canonical: https://harness.fabric.pro/docs/databricks/rag Use Databricks-native Vector Search and Unity AI Gateway for bounded, citation-validated online RAG with MLflow 3 evaluation records. Fabric Harness wires Databricks-native products for retrieval, generation, tracing, and evaluation. It does **not** reimplement Vector Search, Unity AI Gateway, the offline index pipeline, or MLflow judges. Follow the same mental model as the [Databricks AI Cookbook RAG inference chain](https://docs.databricks.com/aws/en/agents/tutorials/ai-cookbook/fundamentals-inference-chain-rag): 1. (Optional) preprocess the user query 2. **Retrieve** with Mosaic Vector Search 3. **Augment** the prompt with retrieved context 4. **Generate** through Unity AI Gateway or a custom Model Serving endpoint 5. Validate inline citations and apply answer limits Offline chunk → embed → index remains a **Databricks Job / notebook / Lakeflow** concern. Quality measurement uses **[MLflow 3 evaluation](https://docs.databricks.com/aws/en/mlflow3/genai/eval-monitor/)** and managed Databricks judges, not a parallel Fabric-only judge product. ## Quick start (managed recipe) ```sh fh add databricks rag-chain # set DATABRICKS_HOST, OAuth credentials, DATABRICKS_VECTOR_INDEX, DATABRICKS_MODEL fh run rag-answer --question "How do I reset my password?" ``` Scaffolded files: | Path | Role | | --- | --- | | `.fabricharness/databricks/rag-chain.ts` | `createRagChain()` + `evaluationArtifacts()` | | `.fabricharness/jobs/rag-answer.ts` | Finite job calling the chain | Related recipes: | Recipe | Use when | | --- | --- | | `fh add vector-search` | Bundle-only / agentic `search` tool (multi-tool agents) | | `fh add databricks rag-chain` | Fixed online inference chain (cookbook path) | | `fh add lakeflow` / Jobs | Offline pipeline ops (index refresh), not the chain itself | ## Code API ### Deterministic chain (cookbook online path) ```ts import { databricksRagChain, toMlflow3EvaluationRecord, exportMlflow3EvaluationJsonl } from '@fabric-harness/databricks'; const chain = databricksRagChain({ databricks: { host: process.env.DATABRICKS_HOST!, principal: { kind: 'pat', token: process.env.DATABRICKS_TOKEN! }, model: 'system.ai.gpt-oss-20b', vectorSearch: { index: 'main.support.kb_index', textColumn: 'chunk', idColumn: 'id', }, }, topK: 5, postProcess: { requireCitations: true, citationRepairAttempts: 2, }, }); const turn = await chain.invoke({ question: 'How do I reset my password?' }); // turn.answer, turn.sources (retrieved), turn.citations (actually referenced), turn.usage ``` With `requireCitations`, Fabric asks the model to revise an uncited answer using only the retrieved source IDs. Repair is bounded and fails closed if the model still omits a valid marker; Fabric never adds a citation to the answer on the model's behalf. What runs under the hood: | Step | Databricks product | Fabric helper | | --- | --- | --- | | Retrieve | Mosaic AI Vector Search | `databricksVectorSearch` / UC principal | | Generate | Unity AI Gateway / Model Serving | `databricksFoundationModelProvider` | | Orchestrate | — | `databricksRagChain` (thin glue only) | ### Agentic multi-tool RAG When the agent must also call SQL, Genie, or Jobs, keep tool-calling: ```ts const chain = databricksRagChain({ databricks: { /* + vectorSearch */ } }); const { modelProvider, tools, policy } = chain.asAgentTools(); // pass into init({ modelProvider, tools, policy }) ``` Or continue using `databricks({ vectorSearch })` + `bundle.tools` as in `examples/with-databricks-rag`. ## Evaluation and quality (native Databricks first) ### Local CI smoke (optional) Lightweight checks on a `RagTurn` validate actual `[source-id]` markers, required facts, and retrieval: ```ts import { scoreRagTurn, toMlflow3EvaluationRecord, exportMlflow3EvaluationJsonl } from '@fabric-harness/databricks'; const scores = scoreRagTurn(turn, { mustContain: ['Settings'], requireCitations: true, mustRetrieveIds: ['doc-1'], }); ``` These are **smoke scorers**, not a substitute for managed evaluation. ### MLflow 3 managed evaluation Export MLflow 3 rows with structured inputs, outputs, expectations, retrieved context, and trace metadata: ```ts const record = toMlflow3EvaluationRecord(turn, { expectedAnswer: 'Use Settings, then Security.', traceId: 'tr-...', submissionId: 'sub-...', }); const jsonl = exportMlflow3EvaluationJsonl([record]); // Merge the record into an MLflow Evaluation Dataset from a Databricks notebook or job. ``` The release certification fixture runs this loop as a serverless Databricks Job. The notebook at `scripts/databricks/rag_evaluation.py` queries five candidates from the real Vector Search index, uses the configured Databricks model to retain only sources that supply relevant or complementary evidence, records the filtered documents in the retriever span, and generates a citation-backed answer. It merges the governed 50-case golden set into a content-hashed Unity Catalog evaluation dataset, so changed fixtures cannot inherit stale rows from an earlier release, and runs the following managed judges: | Case category | Count | Purpose | | --- | ---: | --- | | Factual | 30 | Paraphrased questions over individual governed documents | | Multi-document | 10 | Answers that must combine identity, governance, runtime, deployment, or RAG evidence | | Insufficient context | 5 | Unsupported questions where the correct behavior is to abstain | | Adversarial | 5 | Retrieved prompt-injection text that must be treated as untrusted data | The source documents and cases live together in `scripts/databricks/rag_fixture.json`. The provisioner merges those documents into the Delta source table, triggers the Vector Search index, and embeds the same cases into the evaluation notebook. This prevents the index fixture and golden set from drifting apart. | Judge | What it catches | | --- | --- | | Relevance to query | The answer does not address the request | | Retrieval relevance | Retrieved chunks add irrelevant context | | Retrieval groundedness | Retrieved claims are not supported by the source chunks | | Retrieval sufficiency | Retrieval omitted context needed to answer | | Correctness | The answer misses the expected facts | Each aggregate must meet `DATABRICKS_RAG_EVAL_THRESHOLD` (default `0.8`). Evidence includes the MLflow run, versioned dataset and fixture hash, generation and judge models, category counts, selected aggregate metrics, and threshold result. A failed judge fails the Databricks Job and therefore the release certification gate. The protected release workflow for commit `5fcf927bcf0a3a15e3aec86e2629516cbb76ad26` evaluated fixture `18bd4dffc32c` as MLflow run `18131356bcac4460a26de20d40b9573a` in the reference Azure workspace. It covered all 50 cases with `databricks-gpt-oss-120b` for generation and managed judging, and passed the unchanged `0.8` floor: | Metric | Score | | --- | ---: | | Relevance to query | 0.86 | | Retrieval relevance | 0.895 | | Retrieval groundedness | 0.84 | | Retrieval sufficiency | 0.86 | | Correctness | 0.88 | This is retained workspace evidence for the reference fixture, not a claim that every application or dataset will achieve the same scores. Replace the documents, question-level expected facts, and adversarial cases with your domain corpus before treating the gate as production evidence. ```mermaid flowchart LR G[UC golden dataset
content-hashed version] --> J[Serverless evaluation Job] J --> V[Vector Search
top five candidates] V --> R[Model-assisted reranker
relevant sources only] R --> T[MLflow retriever span] T --> M[AI Gateway generation
bounded context and citations] T --> E[Five MLflow managed judges] M --> E E -->|all scores at least 0.8| P[Release evidence] E -->|score below threshold| F[Block release] F --> D[Diagnose retrieval, prompt, or index] D --> J classDef source fill:#dcfce7,stroke:#16a34a,color:#052e16 classDef gate fill:#fef3c7,stroke:#d97706,color:#422006 classDef fail fill:#fee2e2,stroke:#dc2626,color:#450a0a class G,V,R,T,M source class J,E,P gate class F,D fail ``` Provision or repair the disposable fixture, then run the same gate used by protected CI: ```sh FABRIC_DATABRICKS_PROVISION=1 pnpm databricks:cert:provision pnpm databricks:certify ``` The provisioner is idempotent: it reuses the UC dataset, Jobs, Vector Search index, Feature Serving endpoint, Genie space, and Lakeflow pipeline. Improve quality by diagnosing retrieval versus generation, changing `topK`, prompts, chunks, or embeddings, and rerunning the managed judges before redeploying Apps or Model Serving. The default job limits MLflow to one prediction worker and one scorer worker, with prediction and scorer rate limits, so pay-per-token endpoints do not exceed workspace output-token quotas. Raise `MLFLOW_GENAI_EVAL_MAX_WORKERS`, `MLFLOW_GENAI_EVAL_MAX_SCORER_WORKERS`, `MLFLOW_GENAI_EVAL_PREDICT_RATE_LIMIT`, or `MLFLOW_GENAI_EVAL_SCORER_RATE_LIMIT` only when the workspace uses capacity that supports the additional concurrency. Do **not** build a second full judge product in Fabric when MLflow managed evaluation already exists in the workspace. ## Offline index pipeline (not the chain) Building/updating the Vector Search index (chunking, embedding, write) stays on Databricks: - Notebooks / Jobs - Lakeflow pipelines (`fh add lakeflow`) - UC Volumes as document sources Fabric agents **consume** the index via Vector Search at inference time. ## Guardrails | Concern | Prefer | | --- | --- | | Data access | Unity Catalog grants on the index + service principal | | Tool / SQL risk | `databricks()` governance policy / approvals | | Content policy / gateway | Mosaic AI Gateway / serving policies (configure on the endpoint) | | Audit | MLflow traces (`mlflowTraceExporter`) + lineage hooks | The default chain treats retrieved chunks as untrusted data, serializes them as bounded JSONL, limits per-chunk and total context size, and rejects citation markers that do not match a retrieved source. `turn.sources` means "retrieved"; only source IDs referenced by the final answer appear in `turn.citations`. ```ts const chain = databricksRagChain({ databricks: { /* model + vectorSearch */ }, topK: 5, maxContextChars: 32_000, maxChunkChars: 8_000, postProcess: { citationPolicy: 'validate', maxAnswerChars: 8_000, }, }); ``` ## See also - [Databricks recipes](/docs/databricks/recipes) - [Integrations map](/docs/databricks/integrations) - [example: with-databricks-rag](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-rag) - [Cookbook: RAG chain for inference](https://docs.databricks.com/aws/en/agents/tutorials/ai-cookbook/fundamentals-inference-chain-rag) --- # Databricks recipes (`fh add`) Canonical: https://harness.fabric.pro/docs/databricks/recipes Scaffold Lakebase, SQL, Vector Search, Lakeflow, Jobs, cost controls, and Apps wiring with managed Fabric Harness recipes. Use managed **Databricks recipes** to wire `@fabric-harness/databricks` into an existing project without copying examples by hand. Recipes write versioned files under `.fabricharness/databricks/` (or a job for the analyst composite), update dependencies, and leave secrets in environment variables. Greenfield projects can still start with: ```sh fh init --template databricks ``` Recipes are for **adding one product surface** (or a small workload composite) afterward. Generated Databricks recipes default to `DATABRICKS_MODEL=system.ai.gpt-oss-20b` and `DATABRICKS_INFERENCE_MODE=auto`. Set `DATABRICKS_HOST` to the workspace origin, for example `https://`; do not include `/ai-gateway/mlflow/v1` in the host value. ## List and install ```sh fh add fh add --json fh add databricks core fh add databricks sql fh add lakebase # alias fh add vector-search fh add lakeflow fh add jobs fh add system-tables-cost fh add apps fh add lakehouse # analyst composite (sql + tables) fh add databricks lakebase --dry-run fh update databricks sql ``` ## Catalog | Recipe | Alias examples | Managed files | Dependencies | Maps to | | --- | --- | --- | --- | --- | | `core` | `databricks-core` | `databricks/identity.ts`, `policies/databricks.ts` | `@fabric-harness/databricks` | Workspace identity + UC egress policy | | `sql` | `databricks-sql` | `databricks/sql.ts` | `@fabric-harness/databricks` | SQL Warehouse + table discovery | | `lakebase` | `databricks-lakebase` | `databricks/lakebase.ts` | `@fabric-harness/databricks`, `pg` | Lakebase persistence | | `vector-search` | `databricks-rag` | `databricks/vector-search.ts` | `@fabric-harness/databricks` | Agentic `search` tool bundle | | `rag-chain` | `databricks-rag-chain` | `databricks/rag-chain.ts`, `jobs/rag-answer.ts` | `@fabric-harness/databricks` | Cookbook online chain + MLflow 3 eval export ([RAG docs](/docs/databricks/rag)) | | `lakeflow` | `databricks-dataeng` | `databricks/lakeflow.ts` | `@fabric-harness/databricks` | [with-databricks-dataeng](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-dataeng) | | `jobs` | `databricks-compute` | `databricks/jobs.ts` | `@fabric-harness/databricks` | [with-databricks-compute](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-compute) | | `system-tables-cost` | `databricks-cost` | `databricks/cost.ts` | `@fabric-harness/databricks` | [with-databricks-cost-attribution](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-cost-attribution) | | `apps` | `databricks-app` | `databricks/apps.ts` | `@fabric-harness/databricks`, `@fabric-harness/node` | Apps runtime preset + deploy targets | | `analyst` | `lakehouse` | `jobs/databricks-analyst.ts` | `@fabric-harness/databricks` | init template / simple analyst job | “Lakehouse” is an **alias for the analyst composite** (governed SQL + UC tools), not a separate Databricks SDK product. ## Typical flows ### SQL analyst on Apps ```sh fh init --template minimal fh add databricks core fh add databricks sql fh add databricks analyst # set DATABRICKS_* in .env fh run databricks-analyst --question "What tables are in main?" --mock fh build --target databricks-app ``` ### RAG support agent ```sh # Deterministic cookbook chain (retrieve → augment → generate): fh add databricks rag-chain fh run rag-answer --question "How do I reset my password?" # Or agentic multi-tool search (SQL + search + …): fh add vector-search # Import createVectorSearchBundle() and pass modelProvider + tools into init() ``` See [RAG on Databricks](/docs/databricks/rag) for citation validation, MLflow 3 export, and the quality workflow. ### Lakebase durable state ```sh fh add lakebase # In .fabricharness/config.ts: # import { createLakebasePersistence } from './databricks/lakebase.js'; # persistence: createLakebasePersistence(), ``` ### Data engineering ```sh fh add lakeflow fh add jobs # Use createLakeflowBundle() / createDatabricksJobsTools() in agent init ``` ## Conventions - **Package owns behavior** — recipes only wire `@fabric-harness/databricks` into your workspace. - **Managed markers** — files start with `// fabric-harness-recipe: databricks/@1` for safe `fh update`. - **No secrets in source** — only env names are written to `.env.example`. - **Runnable verification** — generated tests import from `../../.fabricharness/` and are compiled by the recipe contract suite. - **Merge config yourself** — recipes do not silently rewrite `config.ts`; they print paths and env requirements. - **Deploy** — Apps/Serving artifacts still come from `fh build --target databricks-app|databricks-serving`. ## See also - [Databricks quickstart](/docs/databricks/quickstart) - [Integrations map](/docs/databricks/integrations) - [Connector recipes reference](/docs/reference/recipes) - [Examples](/docs/examples) --- # Deployment Overview Canonical: https://harness.fabric.pro/docs/deployment Build targets and where to run them. `fh build` produces a deployable artifact for a chosen target. The same agent code runs on every target — only the build output and runtime differ. ## Targets at a glance | Target | What it produces | Deployment requirement | Best for | | --- | --- | --- | --- | | [Node](/docs/deployment/node) | `dist/server.mjs` HTTP server | Configure auth, durable stores, limits, and monitoring. | Local services, internal automation, container wrapping. | | [Docker](/docs/deployment/docker) | `Dockerfile` + Node bundle | Configure registry, secrets, network policy, and persistent stores. | CI smoke tests, ECS / Kubernetes, generic container hosts. | | [AWS](/docs/deployment/aws) | Docker artifact for ECS/App Runner/EKS | Provision the AWS container control plane around the artifact. | AWS container platforms with external durable state. | | [Fly.io](/docs/deployment/fly) | Docker artifact | Configure the application, secrets, port, health checks, and volume/database. | Regional container applications. | | [Railway](/docs/deployment/railway) | Docker artifact | Configure service variables, port, health checks, and durable database. | Managed container deployment. | | [SST](/docs/deployment/sst) | Node/Docker artifact | Provision a long-running AWS container service. | AWS infrastructure defined from TypeScript. | | [Temporal worker](/docs/deployment/temporal-worker) | Worker entrypoint + activities | Provide a Temporal namespace, task queue, identity, and worker operations. | Durable, replayable, approval-gated agent sessions. | | [Cloudflare Workers + Sandbox](/docs/deployment/cloudflare) | Worker + DO + Sandbox container binding | Configure Worker, Durable Object, R2, and optional Sandbox bindings. | Edge-triggered webhooks, per-tenant Durable Object sessions. | | [Foundry Hosted Agents](/docs/deployment/foundry-hosted-agent) | `Dockerfile` + `azure.yaml` + Bicep; Agent Service client/tools | Configure Azure identity, role assignments, project, networking, and region. | Azure-native agents with per-session isolation, Entra Agent ID, OBO auth. | | [Azure Container Apps / ACI / AKS](/docs/deployment/azure) | ARM control-plane tools; Docker deploy path | Configure subscription identity, registry, network, and compute resources. | Azure customers without Foundry availability or with K8s mandates. | | [Databricks](/docs/deployment/databricks) | Databricks App/Serving artifacts, governed tools, OAuth, and Lakebase stores | Configure workspace identity, UC grants, App resources, and certification. | Data, analytics, ML, and governance agents. | | [E2B](/docs/deployment/e2b) | Control-plane helpers for E2B sandbox runtime | Provide provider credentials and validate lifecycle and cleanup. | Ephemeral sandbox execution for untrusted code. | | [Daytona](/docs/deployment/daytona) | Control-plane helpers for Daytona workspace runtime | Provide provider credentials and validate workspace lifecycle. | Reproducible dev environments as agent sandboxes. | | [Modal](/docs/deployment/modal) | Native TypeScript SDK sandbox adapter | Configure Modal credentials, image, resources, network policy, and cleanup. | Serverless GPU/CPU tasks from agent sessions. | | [Azure Container Instances (ACI)](/docs/deployment/aci) | ARM control-plane helpers for ACI | Configure ACR, managed identity, resource group, and networking. | Lightweight container instances for single-shot agents. | ## How to choose ```mermaid graph TD Start[Where will the agent run?] --> Local{Local / on-prem?} Local -->|Yes| Node Local -->|No| Cloud{Cloud preference?} Cloud -->|Edge / per-tenant| Cloudflare Cloud -->|Azure-native| Azure{Foundry available?} Azure -->|Yes| Foundry[Foundry Hosted Agent] Azure -->|No| ACA[Azure Container Apps] Cloud -->|Data / ML| Databricks[Databricks tools] Cloud -->|Other| Docker[Docker / K8s] Cloud -->|Serverless GPU/CPU| Modal[Modal] Cloud -->|Ephemeral sandbox| E2B[E2B] Cloud -->|Reproducible dev env| Daytona[Daytona] Cloud -->|Lightweight containers| ACI[Azure Container Instances] Node --> Durable{Need durability?} Durable -->|Yes| Temporal[+ Temporal worker] Durable -->|No| NodeOnly[Node server] ``` ## Build invocation ```sh fh build --target node fh build --target docker --docker-build --docker-tag myorg/agents:latest fh build --target temporal-worker fh build --target cloudflare fh build --target foundry-hosted-agent fh build --target databricks-app fh build --target databricks-serving ``` Each target writes to `.fabricharness/build//` and emits a `manifest.json` next to the artifact. See [Build manifest](/docs/reference/build-manifest). For the v2 artifact layout, route examples, and production environment controls, follow [Build and run artifacts](/docs/deployment/build-artifacts). For an end-to-end workspace deployment, use the [Databricks App tutorial](/docs/deployment/databricks-app). ## CI/CD recipes - [GitHub Actions](/docs/deployment/github-actions) — build, sign, deploy on push. - [GitLab CI](/docs/deployment/gitlab-ci) — equivalent pipeline. ## Hardening checklist Before promoting any target to production, walk through [Security hardening](/docs/reference/security-hardening): provenance, attestation, secret handling, network egress, approval gating, and rate limits. --- # Azure Container Instances (ACI) Canonical: https://harness.fabric.pro/docs/deployment/aci Build and deploy Fabric Harness containers to Azure Container Instances with managed identity. The Azure Container Instances (ACI) target builds the image in Azure Container Registry and creates or replaces a public ACI container group using managed identity for image pull. ## Generated artifact - `AciBuildPlugin` integrates ACI with the build pipeline. - `aciReadme()` generates resource and deployment instructions. - `AciDriver` exposes the ACI deploy orchestration contract. ## Build invocation ```sh fh build --target aci ``` This emits `README.aci.md` with the exact image, resource, and ARM deployment steps for the artifact. ## Deploy The ACI target is registered in the build system and deploy registry. Use it like any other target: ```sh export AZURE_ACR_NAME=fabricregistry export AZURE_RESOURCE_GROUP=agents-rg export AZURE_CONTAINER_NAME=support-agent export AZURE_LOCATION=westus3 fh deploy --target aci ``` Optional variables: | Variable | Default | | --- | --- | | `AZURE_ACR_REPOSITORY` | `fabric-harness` | | `AZURE_IMAGE_TAG` | `latest` | | `AZURE_ACI_IDENTITY` | `[system]` | | `AZURE_ACI_CPU` | `1` | | `AZURE_ACI_MEMORY_GB` | `1.5` | | `AZURE_ACI_DNS_NAME_LABEL` | unset | | `PORT` | `3000` | Grant the selected managed identity `AcrPull` on the registry. `fh deploy --preview --target aci` prints the commands without changing Azure resources. Runtime secrets remain external to the build; configure them through your deployment environment or secret delivery path. ## See also - [Deployment overview](/docs/deployment) - [Azure Container Apps / ACI / AKS](/docs/deployment/azure) - [Azure Container Instances documentation](https://learn.microsoft.com/azure/container-instances/) --- # AWS Canonical: https://harness.fabric.pro/docs/deployment/aws Deploy a Fabric Docker artifact to ECS, App Runner, or EKS. Build and push the Docker target: ```sh fh build --target docker --docker-build --docker-tag "$ECR_REPOSITORY:$GIT_SHA" ``` Run the image on ECS/Fargate, App Runner, or EKS with port `3000`, `NODE_ENV=production`, a nonempty `FABRIC_HARNESS_API_TOKEN`, model credentials, and an external Postgres session store. Configure health checks at `/health` and readiness at `/ready`. Use task roles/IRSA instead of static AWS credentials, Secrets Manager or Parameter Store for secrets, private networking and egress controls, immutable image digests, least-privilege IAM, centralized logs/traces, and separate task definitions per environment. Verify restart recovery, approval state, rate limits, and artifact access before production. --- # Azure Canonical: https://harness.fabric.pro/docs/deployment/azure Azure OpenAI, Key Vault, Blob artifacts, Container Apps Jobs, ACI, AKS, and Foundry Agent Service helpers. Fabric Harness keeps Azure-specific integration code in `@fabric-harness/azure` so the core SDK stays provider-neutral. The package includes model, storage, secret, Foundry Agent Service, and Azure ARM control-plane helpers. Use `aksSandbox` for a full pod-backed `SandboxEnv`; use the ACA and ACI helpers for build and control-plane workflows. ## Install ```sh npm install @fabric-harness/azure @fabric-harness/sdk ``` ## Azure OpenAI Use Azure OpenAI as a model provider: ```ts import { AzureOpenAIModelProvider } from '@fabric-harness/azure'; const provider = new AzureOpenAIModelProvider({ endpoint: process.env.AZURE_OPENAI_ENDPOINT!, apiKey: process.env.AZURE_OPENAI_API_KEY!, deployment: process.env.AZURE_OPENAI_DEPLOYMENT!, }); const fabric = await init({ modelProvider: provider }); ``` The provider sends chat-completions requests to: ```txt /openai/deployments/{deployment}/chat/completions ``` ## Blob artifact store ```ts import { createAzureBlobArtifactStore } from '@fabric-harness/azure'; const blobs = createAzureBlobArtifactStore({ accountUrl: 'https://acct.blob.core.windows.net', container: 'fabric-artifacts', token: async () => getAzureAccessToken(), }); await blobs.put('reports/triage.md', markdown, 'text/markdown'); ``` Use this when Fabric session artifacts need to land in Azure Storage. The helper is intentionally narrow; session-store integration and retention policy are controlled by your app. ## Key Vault secret resolver ```ts import { azureKeyVaultSecretProvider } from '@fabric-harness/azure'; import { defineCommand, secret, secretResolver } from '@fabric-harness/sdk'; const keyVault = azureKeyVaultSecretProvider({ vaultUrl: process.env.AZURE_KEY_VAULT_URL!, token: async () => getAzureAccessToken(), }); const gh = defineCommand('gh', { env: { GH_TOKEN: secret('github-token') }, }); const fabric = await init({ resolveSecret: secretResolver(keyVault) }); ``` Secrets stay in Key Vault and are resolved only at command execution time. ## Azure ARM client ```ts import { createAzureArmClient } from '@fabric-harness/azure'; const arm = createAzureArmClient({ subscriptionId: process.env.AZURE_SUBSCRIPTION_ID!, token: async () => getAzureAccessToken(), }); ``` The ARM client is used by the control-plane tools below. ## Container Apps Jobs ```ts import { azureContainerAppsJobTool } from '@fabric-harness/azure'; const tools = [azureContainerAppsJobTool(arm)]; ``` The tool starts an existing Azure Container Apps Job. It is useful when a Fabric agent should trigger a prebuilt containerized workload, for example a data import, validation run, or batch repair. Input shape: ```ts { resourceGroup: string; name: string; environmentVariables?: Record; } ``` ## AKS sandbox (`SandboxEnv`) Run agent code inside an AKS pod via `aksSandbox`. This is the full sandbox interface — `exec`/`readFile`/`writeFile`/`mkdir`/`rm` all work — backed by `@kubernetes/client-node` against credentials pulled from Azure ARM. ```ts import { init } from '@fabric-harness/sdk'; import { createAzureArmClient } from '@fabric-harness/azure'; import { aksSandbox } from '@fabric-harness/azure/aks-sandbox'; const arm = createAzureArmClient({ subscriptionId: process.env.AZURE_SUBSCRIPTION_ID!, token: process.env.AZURE_ACCESS_TOKEN!, }); const sandbox = await aksSandbox({ arm, resourceGroup: 'my-rg', clusterName: 'my-aks', // Either attach to an existing pod: podName: 'agent-pod', // Or create an ephemeral pod from an image (auto-deleted on cleanup): // image: 'alpine:latest', }); const fabric = await init({ sandbox }); ``` Requires `@kubernetes/client-node` as a peer dependency. ## AKS Run Command (control-plane tool) For invoking AKS Run Command from the agent's tool surface (without entering a pod): ```ts import { azureAksRunCommandTool } from '@fabric-harness/azure'; const tools = [azureAksRunCommandTool(arm)]; ``` Treat this as a privileged `execute` effect and guard it with policy: ```ts const policy = { toolPolicy: { requireApproval: ['azure_aks_run_command'], }, }; ``` ## Azure Container Instances exec ```ts import { azureContainerInstanceExecTool } from '@fabric-harness/azure'; const tools = [azureContainerInstanceExecTool(arm)]; ``` The ACI tool creates an exec session for a configured container group/container. It is a control-plane primitive, not a complete remote shell stream. Use it for operator workflows where your app handles the returned exec session details. ## Build targets Two Azure-specific build targets emit deployable artifacts: ### `--target aks` ```sh fh build --target aks ``` Emits Dockerfile, `.dockerignore`, and `k8s/` manifests: - `k8s/deployment.yaml` — Deployment with `/health` and `/ready` probes - `k8s/service.yaml` — ClusterIP Service on port 80 → 3000 - `k8s/README.md` — push-and-apply walkthrough Build the image, push to your ACR, then `kubectl apply -f k8s/`. ### `--target aca` (Azure Container Apps) ```sh fh build --target aca azd up ``` Emits Dockerfile, `azure.yaml` (azd project), and `infra/` Bicep: - `infra/main.bicep` — managed environment + ACR (Basic) + Container App with scale-to-zero - `infra/main.parameters.json` — `azd`-driven parameters - `infra/README.md` — `azd up` walkthrough The Container App ingresses externally on port 3000 with `/health` and `/ready` probes. ## Foundry Agent Service For Foundry Agent Service invocation and lifecycle helpers, see [Foundry Hosted Agents](/docs/deployment/foundry-hosted-agent). ## Live tests Live tests are skipped unless enabled: ```sh FABRIC_AZURE_OPENAI_TEST=1 \ AZURE_OPENAI_ENDPOINT=... \ AZURE_OPENAI_API_KEY=... \ AZURE_OPENAI_DEPLOYMENT=... \ pnpm --filter @fabric-harness/azure test ``` ```sh FABRIC_AZURE_FOUNDRY_TEST=1 \ AZURE_FOUNDRY_PROJECT_ENDPOINT=... \ AZURE_FOUNDRY_AGENT_ID=... \ AZURE_TOKEN=... \ pnpm --filter @fabric-harness/azure test ``` ```sh FABRIC_AZURE_ARM_TEST=1 \ AZURE_SUBSCRIPTION_ID=... \ AZURE_TOKEN=... \ pnpm --filter @fabric-harness/azure test ``` Optional ARM resources: ```sh AZURE_CONTAINER_APPS_JOB_RESOURCE_GROUP=... AZURE_CONTAINER_APPS_JOB_NAME=... AZURE_AKS_RESOURCE_GROUP=... AZURE_AKS_CLUSTER_NAME=... AZURE_ACI_RESOURCE_GROUP=... AZURE_ACI_CONTAINER_GROUP=... AZURE_ACI_CONTAINER_NAME=... ``` --- # Build and Run Artifacts Canonical: https://harness.fabric.pro/docs/deployment/build-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/`: ```ts title=".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/`: ```ts title=".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 ```sh fh build --target node ``` The build summary distinguishes the two surfaces: ```text 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: ```text .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: ```sh 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: ```sh curl -sS http://localhost:8080/jobs/summarize \ -H 'content-type: application/json' \ -d '{"text":"Fabric builds durable agents."}' ``` ```json { "result": "Fabric is a framework for durable agents.", "runId": "8f2d...", "agentPath": "/app/.fabricharness/jobs/summarize.mjs" } ``` Persistent input is admitted durably and returns `202`: ```sh curl -i http://localhost:8080/agents/support/acct-42 \ -H 'content-type: application/json' \ -d '{"message":"Where is my invoice?"}' ``` ```json { "submissionId": "5ec1...", "streamUrl": "/agents/support/acct-42/conversation?session=default&offset=0", "offset": "0" } ``` Use `@fabric-harness/client` to wait for settlement: ```ts 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: ```sh 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](/docs/reference/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: ```sh 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: ```sh 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 ```sh 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 ```sh fh build --target docker --provenance --attestation --sbom fh verify-provenance .fabricharness/build/docker fh verify-attestation .fabricharness/build/docker ``` See [Docker](/docs/deployment/docker), [Databricks App](/docs/deployment/databricks-app), and [Security hardening](/docs/reference/security-hardening). --- # Cloudflare Workers + Sandbox Canonical: https://harness.fabric.pro/docs/deployment/cloudflare Deploy to Cloudflare Workers with Durable Object sessions and Cloudflare Sandbox. The Cloudflare target emits an unbundled Worker entrypoint, a Durable Object session store, and a Cloudflare Sandbox container binding for finite jobs and persistent agents. Wrangler owns the Cloudflare bundle; Fabric Harness emits the entry, bindings, and config. Each persistent agent/instance pair is routed to one Durable Object, which serializes turns and persists named sessions in Durable Object SQLite. Before production, run the live smoke workflow in your Cloudflare account to validate bindings, Durable Object persistence, R2 access, and Sandbox container startup with your account limits. ## Quickstart ```sh npx @fabric-harness/cli init --template cloudflare --dir my-edge-agent cd my-edge-agent npm install npx fabric-harness dev --target cloudflare --port 8787 ``` The template uses the Cloudflare Workers AI binding (`env.AI`) so the Worker can run inference without external model API keys. ## Build ```sh fh build --target cloudflare cd .fabricharness/build/cloudflare npm install ``` Output: ``` .fabricharness/build/cloudflare/ dist/worker.ts wrangler.jsonc Dockerfile # default Cloudflare Sandbox image manifest.json README.cloudflare.md ``` ## Develop locally ```sh fh dev --target cloudflare ``` This wraps `wrangler dev` so the same bundler runs in dev and deploy. Two reload paths run simultaneously: - **Wrangler** watches the bundle's transitive import graph and reloads `workerd` on body edits to your agent files. - **Fabric Harness's structural watcher** watches `.fabricharness/jobs/` and `.fabricharness/agents/`, then regenerates the entry whenever the definition set changes. Net result: you can edit job bodies, add jobs, or change `triggers: { webhook: true }` without restarting the dev server. Or if you've already built once and just want raw wrangler: ```sh npx wrangler dev ``` ## Deploy ```sh fh build --target cloudflare cd .fabricharness/build/cloudflare npx wrangler deploy ``` ## Routes The Cloudflare Worker exposes both execution models: - `GET /health` · `GET /ready` · `GET /manifest` - `POST /jobs/:name` — invoke a finite job and receive `{ result, runId }` - `POST /jobs/:name?wait=false` — admit an asynchronous run; an `Idempotency-Key` reuses its run - `GET /runs/:runId` · `GET /runs/:runId/events?offset=0` — inspect finite runs and events - `POST /runs/:runId/abort` — abort an active finite run - `POST /agents/:name/:instanceId?wait=false` with `{ message, session? }` — durably admit a persistent submission - `GET /agents/:name/:instanceId/submissions/:submissionId?session=default` — inspect settlement - `GET /agents/:name/:instanceId/conversation?session=default&offset=0` — read the offset stream - `POST /agents/:name/:instanceId/abort?session=default` — abort queued or active submissions - `GET /agents/:name/:instanceId?session=default` — inspect a named persistent session - `DELETE /agents/:name/:instanceId?session=default` — delete that named session - `GET /sessions/:runId` — inspect the stored run - `GET /sessions/:runId/timeline` · `/metrics` · `/tasks` · `/approvals` · `/artifacts` Webhook trigger gating applies on the Worker too: in production mode (`FABRIC_ENV=production`), only definitions with `triggers.webhook === true` are exposed publicly. ## Cron Triggers Declare schedules on finite jobs: ```ts export default job({ name: 'daily-report', triggers: { schedule: '0 16 * * 1-5' }, // Cloudflare cron is UTC async run({ input }) { return buildReport(input); }, }); ``` The Cloudflare build adds each unique expression to `wrangler.jsonc` and emits `scheduled()`. Scheduled runs use deterministic IDs, persist run state and offset events in a per-run Durable Object, and deduplicate repeated delivery of the same occurrence. HTTP and cron execution share the same job function and `{ runId, acceptedAt, statusUrl, eventsUrl }` receipt contract. ## Choosing a Cloudflare sandbox mode Fabric Harness supports two Cloudflare sandbox modes. ### Containers / Cloudflare Sandbox Use this mode when your agent needs Linux shell commands, package managers, language toolchains, `bash`, `grep`, `read`, `write`, and `edit` tools. ```ts export default { sandbox: { backend: 'cloudflare', mode: 'sandbox', binding: 'Sandbox', cwd: '/workspace', }, }; ``` ### Shell Workspace / `@cloudflare/shell` Use this mode when you want a lightweight durable Workspace and a Worker Loader-backed JavaScript `code` tool. It does **not** provide Linux shell commands; the model uses `state.*` inside the `code` tool. ```ts export default { sandbox: { backend: 'cloudflare', mode: 'shell-workspace', loaderBinding: 'LOADER', cwd: '/', }, }; ``` The generated `wrangler.jsonc` adds: ```jsonc { "worker_loaders": [{ "binding": "LOADER" }] } ``` Helpers for custom setup and explicit R2 hydration are exported from `@fabric-harness/cloudflare/shell`. If Cloudflare sandboxing isn't configured, the Worker falls back to Fabric's empty sandbox. The build always gives an explicitly configured Cloudflare sandbox precedence over the lightweight `virtual` default added by `agent()`. This means the same agent source can stay infrastructure-free locally and use the container or Shell Workspace selected by the deployment target without changing its prompt and session code. ## Verify the Sandbox contract The runnable `examples/with-cloudflare-sandbox` project includes a `sandbox-certification` job. It exercises the same nine behaviors used by the other maintained remote sandbox adapters: - shell execution and binary file round trips; - working-directory and environment propagation; - stdout/stderr collection; - timeout and abort, including termination of the native Cloudflare process; - portable reference encoding, reconnect, and provider cleanup. Build and run the Worker locally with the actual Cloudflare Sandbox container: ```bash cd examples/with-cloudflare-sandbox pnpm fh build --target cloudflare cd .fabricharness/build/cloudflare pnpm exec wrangler dev --local ``` In another terminal, request the report: ```bash curl --fail --request POST http://localhost:8787/jobs/sandbox-certification \ --header 'content-type: application/json' \ --data '{"input":{"credentialed":false}}' ``` The response contains a schema-v1, secret-free report with one result per check. A passing report has `ok: true`, `provider: "cloudflare-sandbox"`, and nine `status: "passed"` rows. Protected live CI uses `credentialed: true` and retains the same JSON as certification evidence. The generated container image and `@cloudflare/sandbox` dependency are kept on the supported 0.9 line so local and hosted results exercise the same SDK contract. ## How it maps to Fabric primitives | Fabric primitive | Cloudflare equivalent | | --- | --- | | Session store | Durable Object SQLite (one DO per persistent agent instance; finite runs route by run id) | | `SandboxEnv` | Cloudflare Sandbox container binding via `@cloudflare/sandbox`, or Cloudflare Shell Workspace via `@cloudflare/shell` | | Model provider | `env.AI` Workers AI binding via `CloudflareWorkersAIModelProvider` (optional; HTTP providers also work) | | Webhook trigger | `POST /jobs/:name` | | Schedule trigger | Worker `scheduled()` + `triggers.crons` | | Persistent prompt | `POST /agents/:name/:instanceId` | | Health/manifest | `GET /health`, `GET /manifest` | ## Workers AI binding (no API tokens) `@fabric-harness/cloudflare/workers-ai` ships a `ModelProvider` that routes inference through `env.AI.run()` instead of HTTP. Zero API tokens, zero egress, runs at the edge. Workers AI accepts the OpenAI Chat Completions request body, so the provider serializes through the SDK's standard OpenAI helpers. ```ts import { CloudflareWorkersAIModelProvider } from '@fabric-harness/cloudflare/workers-ai'; export default { async fetch(request: Request, env: Env) { const fabric = await init({ modelProvider: new CloudflareWorkersAIModelProvider({ binding: env.AI, defaultModel: '@cf/meta/llama-3.1-8b-instruct', // Optional: route through Cloudflare AI Gateway // gateway: { id: 'my-gateway', skipCache: false, cacheTtl: 3600 }, }), }); // ... }, }; ``` Add the binding to `wrangler.jsonc`: ```jsonc { "ai": { "binding": "AI" } } ``` You can still use HTTP providers (Anthropic, OpenAI-compatible, Vercel AI Gateway) on the Cloudflare target — store the API key as a [Workers Secret](https://developers.cloudflare.com/workers/configuration/secrets/) and reference it via `env`. ## Account validation Run the Cloudflare smoke in your account before rollout: ```sh FABRIC_CLOUDFLARE_TEST=1 \ FABRIC_CLOUDFLARE_WORKER_URL=https://..workers.dev \ FABRIC_CLOUDFLARE_ARTIFACT_DIR=/path/to/.fabricharness/build/cloudflare \ CLOUDFLARE_ACCOUNT_ID= \ pnpm --filter @fabric-harness/cloudflare test -- live.test.ts ``` The repository test suite also runs local workerd tests without account credentials. It admits concurrent persistent requests, verifies FIFO and attachment materialization, aborts finite and persistent work, deletes a session, kills workerd during a tool call, restarts with the same Durable Object storage, and verifies conservative interrupted-tool settlement. The account smoke adds the provider-specific bindings, Sandbox container, and R2 checks. ## Limits and considerations - Worker request/CPU limits apply; long-running workflows belong on the Temporal worker target. - Persistent turns use a Durable Object submission queue with leases, FIFO execution, restart reconciliation, attachment materialization, abort, deletion, and offset conversation streams. - Use Temporal when workflows exceed Worker or Durable Object execution/storage limits, require long timers across many external activities, or need Temporal's workflow-history tooling. - Sandbox container start latency depends on the Cloudflare image; warm pools help. - Keep `FABRIC_HARNESS_API_TOKEN`, body limits, rate limits, and `FABRIC_ENV=production` trigger gating enabled for public deployments. - With `FABRIC_ENV=production`, an unset API token fails closed: only `/health` and `/ready` remain reachable without authentication. --- # Databricks Canonical: https://harness.fabric.pro/docs/deployment/databricks First-party Databricks integration — Unity AI Gateway, Unity Catalog governance, RAG, Lakebase, deploy targets, and cost reconciliation. `@fabric-harness/databricks` is a **first-party integration** for building governed agents that consume Databricks services. One `databricks()` call wires Unity AI Gateway, governed SQL, Unity Catalog, AI Functions, Genie, Lakeflow, Vector Search, Feature Serving, governance policy, and cost reporting under a single principal. Everything is layered **on top of** Unity Catalog; it never re-implements UC permissions. For the product-level architecture and complete feature map, start with [Fabric Harness on Databricks](/docs/databricks). This page is the deployment and package reference. For Lakebase, set `DATABRICKS_LAKEBASE_ENDPOINT` (or `ENDPOINT_NAME`) to the full `projects/.../branches/.../endpoints/...` resource name. Run the App, OBO, permission-denial, and Lakebase restart certification suite in the target workspace before rollout. See Databricks' [Node/manual credential flow](https://docs.databricks.com/aws/en/oltp/projects/external-apps-manual-api). For a complete scaffold-to-deploy walkthrough, start with the [Databricks App tutorial](/docs/deployment/databricks-app). ## The bundle ```ts import { databricks } from '@fabric-harness/databricks'; const dbx = databricks({ host: process.env.DATABRICKS_HOST!, // UC enforces this principal's grants on every call. principal: { kind: 'service-principal', host: process.env.DATABRICKS_HOST!, clientId: process.env.DATABRICKS_CLIENT_ID!, clientSecret: process.env.DATABRICKS_CLIENT_SECRET!, }, model: 'system.ai.gpt-oss-20b', warehouseId: process.env.DATABRICKS_WAREHOUSE_ID!, vectorSearch: { index: 'main.kb.docs_index', textColumn: 'chunk', idColumn: 'id' }, // RAG aiFunctions: true, // ai_query() genie: { spaceId: 'space-123' }, // NL → SQL analytics lakeflow: true, // pipeline tools consumption: true, // System-Tables cost reporting featureServing: { endpoint: 'user-features' }, governance: { stewardAudience: 'data-steward', onLineage: (r) => console.log('[lineage]', r) }, }); // dbx.modelProvider, dbx.tools, dbx.policy, dbx.retriever, dbx.consumption, dbx.store, dbx.identity ``` Provider-qualified model refs also resolve through the generic SDK path. For the default Gateway model, use `FABRIC_MODEL=databricks/system.ai.gpt-oss-20b`. Inside `databricks()` or `defineDatabricksAgent()`, use the native service name `system.ai.gpt-oss-20b`. ## Unity AI Gateway and custom endpoints `databricksFoundationModelProvider({ host, token })` uses Unity AI Gateway for `system.ai.*` model services and retains `/serving-endpoints` for explicitly selected custom endpoints. Both paths are OpenAI-compatible and inherit streaming, tool calls, and reasoning content. The same rotating token threads through every REST and data call. ## Identity & Unity Catalog governance `databricksIdentity()` produces a rotating token from a **PAT**, an **OAuth service principal** (cached + refreshed), or **on-behalf-of** a specific end user. UC enforces that principal's table/row/column grants natively — the agent physically cannot read what it lacks `SELECT` on. On top of UC, Fabric adds: - **Lineage/audit** — `withGovernance()` stamps every tool call (principal, service, catalog/schema) to an `onLineage` sink (secrets redacted). - **Approval routing** — `databricksGovernancePolicy()` routes sensitive (write/execute) tools to a steward audience via `CapabilityPolicy.approvalRules`. - **Egress allowlist** — outbound network pinned to the workspace host. With channel `actor` propagation, an agent can act **on behalf of** the human who triggered it, so UC enforces that user's grants — per-user data boundaries with no per-user policy code. ## Tools | Tool | What | | --- | --- | | `databricks_sql` | Run SQL on a warehouse (governed). | | `databricks_unity_catalog_tables` / `databricks_table_info` | Discover + describe UC tables. | | `search` (Vector Search) | RAG retrieval over a Mosaic AI Vector Search index. | | `databricks_ai_query` | In-warehouse `ai_query()` model inference (parameterized, injection-safe). | | `databricks_genie_ask` | NL → SQL analytics over an AI/BI Genie space. | | `databricks_pipeline_*` | List / status (read) + start / stop (execute) Lakeflow pipelines. | | `databricks_feature_lookup` | Low-latency feature lookup from a Feature Serving endpoint. | | `databricks_consumption` | Real DBUs + list cost from `system.billing` System Tables. | Job, notebook, and MLflow tools (`databricksRunJobTool`, `databricksNotebookTool`, `databricksMlflowLogMetricTool`) and the SQL-warehouse `SandboxEnv` (`databricksSqlSandbox`, `@fabric-harness/databricks/sql-sandbox`) remain available as building blocks. ## State — Lakebase Lakebase is Postgres-compatible, and Fabric's Postgres session, submission, and conversation-stream stores are structurally reusable there. `lakebaseClient()` exchanges workspace OAuth for a database credential, caches it until the early-refresh window, and single-flights refreshes. Configure the workspace host and full endpoint resource name; never pass the workspace token as a direct database password. `databricksApp().serverOptions()` supplies all three stores to the shared Node server. ```ts import { databricksIdentity, databricksPersistence } from '@fabric-harness/databricks'; import { startDevServer } from '@fabric-harness/node'; const identity = databricksIdentity({ kind: 'service-principal', host: process.env.DATABRICKS_HOST!, clientId: process.env.DATABRICKS_CLIENT_ID!, clientSecret: process.env.DATABRICKS_CLIENT_SECRET!, }); const persistence = databricksPersistence({ lakebase: { workspaceHost: process.env.DATABRICKS_HOST!, endpoint: process.env.DATABRICKS_LAKEBASE_ENDPOINT!, host: process.env.DATABRICKS_LAKEBASE_HOST!, database: process.env.DATABRICKS_LAKEBASE_DATABASE!, user: process.env.DATABRICKS_LAKEBASE_USER!, token: identity, }, }); await persistence.migrate(); const { store, submissionStore, conversationStreamStore } = await persistence.connectAll(); await startDevServer({ sessionStore: store, submissionStore, conversationStreamStore, }); ``` For local Postgres tests, use the explicit `password` option instead of `token`, `workspaceHost`, and `endpoint`. Production Lakebase should always use credential exchange. ## Safe REST retries Reads retry transient network, `429`, and `5xx` failures by default. Mutating `POST` requests do not retry unless they carry a native Databricks idempotency token, an `Idempotency-Key`, or are explicitly classified safe. ```ts import { createDatabricksClient } from '@fabric-harness/databricks'; const client = createDatabricksClient({ host: process.env.DATABRICKS_HOST!, token: identity, maxRetries: 3, }); // Safe read: retries transient failures. await client.get('/api/2.1/unity-catalog/catalogs'); // Unprotected mutation: one attempt only. await client.post('/api/2.0/sql/statements', { warehouse_id: process.env.DATABRICKS_WAREHOUSE_ID, statement: 'SELECT 1', }); // Databricks-native token: retries reuse the same token and cannot create a second run. await client.post('/api/2.1/jobs/run-now', { job_id: 42, idempotency_token: crypto.randomUUID(), }); // Generic idempotency header for an endpoint that honors it. await client.post('/api/custom/mutation', { value: 1 }, {}, { idempotencyKey: 'operation-42', }); ``` `databricksRunJobTool()` and `databricksNotebookTool()` generate native idempotency tokens automatically. Do not set `retry: 'always'` unless the target operation is independently idempotent. ## Deploy targets Build a deployable artifact with `fabric-harness build --target `: - **`databricks-app`** — intended to run the agent in-workspace as a Databricks App (the app's service principal is the acting UC identity). Emits `app.yaml` (bridges `DATABRICKS_APP_PORT`) + deploy docs. `fh deploy` synchronizes the Asset Bundle, creates a new App snapshot, and waits for the snapshot to reach `SUCCEEDED` before returning. App artifacts omit bundled definition source maps because Databricks rejects individual snapshot files larger than 10 MiB; executable `.mjs` bundles remain included. When `BUNDLE_VAR_lakebase_endpoint` and `BUNDLE_VAR_lakebase_database_resource` are set at build time, the bundle attaches Lakebase Autoscaling as a managed `postgres` App resource. The database variable is the **database resource name**, not the PostgreSQL database name: ```sh export BUNDLE_VAR_lakebase_endpoint='projects/my-project/branches/production/endpoints/primary' export BUNDLE_VAR_lakebase_database_resource='projects/my-project/branches/production/databases/app-db' fh deploy --target databricks-app ``` Find both values with `databricks postgres list-endpoints ` and `databricks postgres list-databases `. The database API response separately reports the PostgreSQL name under `status.postgres_database`; Databricks injects that value as `PGDATABASE`. It also injects the other `PG*` connection variables on every start, while `app.yaml` resolves the endpoint path with `valueFrom`. No database password or workspace token is stored in the artifact. - **`databricks-serving`** — proxy-only: packages an MLflow `ChatAgent` proxy to an agent deployed elsewhere, registers it to Unity Catalog, and creates a serving endpoint that **Agent Bricks and the Playground** consume as a model/tool. `predict_stream` maps the Harness SSE `text_delta` stream to MLflow `ChatAgentChunk` values with stable message IDs; `predict` aggregates that same stream. It does not execute the TypeScript agent inside Model Serving. Node-derived targets use the same v2 server as local development, so finite jobs keep `/jobs/:name` and persistent agents keep durable `/agents/:name/:id` admission after deployment. Cloudflare uses its own Durable Object-backed persistent runtime; Node-derived targets use the shared submission server. ## Cost reconciliation `databricksTenantCostLimit()` enforces a `perScope` budget against **real** Databricks spend from System Tables (estimates still guard `perCall`/`perSession`). System Tables are delayed accounting, so this is a reconciliation guard rather than a real-time kill switch. See [Cost attribution](/docs/operating/cost-attribution). ## See also - Examples: `with-databricks` (analytics copilot), `with-databricks-rag` (support agent), `with-databricks-dataeng` (pipelines), `with-databricks-cost-attribution`. - [Model providers](/docs/building/model-providers) · [Channels](/docs/building/channels) · [Cost attribution](/docs/operating/cost-attribution) --- # Daytona Canonical: https://harness.fabric.pro/docs/deployment/daytona Reproducible dev environments as agent sandboxes. Use a [Daytona](https://daytona.io) workspace as a remote `SandboxEnv` while the Fabric Harness runtime runs in your Node deployment. ## Install ```sh npm install @fabric-harness/connectors @daytona/sdk ``` ## Connect a sandbox ```ts import { Daytona } from '@daytona/sdk'; import { daytonaSandbox } from '@fabric-harness/connectors'; import { init } from '@fabric-harness/sdk'; const daytona = new Daytona({ apiKey: process.env.DAYTONA_API_KEY }); const remote = await daytona.create({ image: 'ubuntu:latest' }); const fabric = await init({ sandbox: daytonaSandbox(remote, { cleanup: true }), }); const session = await fabric.session(); const result = await session.prompt('Inspect the repository and run its test suite.'); ``` Use `daytonaSandboxFactory()` when each session should create and own a separate workspace. The adapter supports `@daytona/sdk >=0.195.0 <1`. It converts Fabric's millisecond timeout to Daytona's seconds-based process timeout and exposes `remote.id` as a portable workspace reference. ```ts registerStandardSandboxRefDecoders({ daytona: { connect: ({ workspaceId }) => daytona.get(workspaceId) }, }); ``` Use [`assertSandboxCertification()`](/docs/building/sandbox-connectors#certification-helper) to verify binary files, cwd/env, output callbacks, timeout, abort, reconnect, and deletion with the target account. ## Environment variables - `DAYTONA_API_KEY` — Daytona API key (read lazily at runtime) ## See also - [Deployment overview](/docs/deployment) - [Sandbox connectors](/docs/building/sandbox-connectors#daytona) - [Daytona documentation](https://daytona.io/docs) --- # Docker Canonical: https://harness.fabric.pro/docs/deployment/docker Emit a Dockerfile and (optionally) build/push the image. The Docker target emits a `Dockerfile` plus the Node bundle. With `--docker-build` and `--docker-push`, the CLI invokes Docker for you. ## Build the scaffold ```sh fh build --target docker ``` Output: ``` .fabricharness/build/docker/ .fabricharness/ agents/*.mjs jobs/*.mjs Dockerfile dist/ agents/*.mjs jobs/*.mjs server.mjs manifest.json package.json README.docker.md ``` The definition modules and embedded workspace are runtime inputs. Copy or publish the complete build directory; `dist/server.mjs` alone is not a complete deployment artifact. ## Build the image in one step ```sh fh build --target docker --docker-build --docker-tag myorg/agents:0.1.0 ``` Add `--docker-push` to push immediately: ```sh fh build --target docker --docker-build --docker-push --docker-tag myorg/agents:0.1.0 ``` ## SBOM and provenance The CLI integrates with Syft and cosign: ```sh fh build --target docker \ --docker-build --docker-tag myorg/agents:0.1.0 \ --image-sbom --image-sbom-required \ --provenance --sign-provenance --signing-key env://COSIGN_PRIVATE_KEY ``` Verify later with: ```sh fh verify-attestation .fabricharness/build/docker fh verify-provenance .fabricharness/build/docker ``` ## Running the image ```sh docker run --rm -p 8080:8080 \ -e PORT=8080 \ -e OPENAI_API_KEY=$OPENAI_API_KEY \ myorg/agents:0.1.0 ``` ## Sandbox notes The Docker *target* (the host container) and the Docker *sandbox* (per-session isolation inside that host) are different things: - **Target Docker** = where the Fabric server runs. - **Sandbox Docker** = a separate Docker container started per session for tool execution. Requires Docker socket access from the host. For most production deployments, use a non-Docker sandbox (e.g. Cloudflare Sandbox at the edge, or Foundry Hosted Agents on Azure) and reserve Docker for the host. --- # E2B Canonical: https://harness.fabric.pro/docs/deployment/e2b Ephemeral sandbox execution for untrusted code. Use [E2B](https://e2b.dev) as an ephemeral `SandboxEnv` for code execution while the Fabric Harness runtime runs in your Node deployment. ## Install ```sh npm install @fabric-harness/connectors @e2b/code-interpreter ``` ## Connect a sandbox ```ts import { Sandbox } from '@e2b/code-interpreter'; import { e2bSandbox } from '@fabric-harness/connectors'; import { init } from '@fabric-harness/sdk'; const remote = await Sandbox.create(); const fabric = await init({ sandbox: e2bSandbox(remote, { cleanup: true }), }); const session = await fabric.session(); const result = await session.prompt('Run the supplied Python analysis and summarize the output.'); ``` The adapter maps E2B commands and file operations to the common Fabric sandbox interface. Keep `cleanup: true` for per-session environments. The supported range is `@e2b/code-interpreter >=2.6.1 <3`. Native command callbacks, timeout, pause/resume, binary files, and the provider `sandboxId` are mapped to the Fabric contract. ```ts registerStandardSandboxRefDecoders({ e2b: { connect: ({ sandboxId }) => Sandbox.connect(sandboxId) }, }); ``` Use [`assertSandboxCertification()`](/docs/building/sandbox-connectors#certification-helper) to produce the credentialed, secret-free CI evidence file. ## Environment variables - `E2B_API_KEY` — E2B API key (read lazily at runtime) ## See also - [Deployment overview](/docs/deployment) - [Sandbox connectors](/docs/building/sandbox-connectors#e2b) - [E2B documentation](https://e2b.dev/docs) --- # Fly.io Canonical: https://harness.fabric.pro/docs/deployment/fly Deploy the Fabric Docker target to Fly.io. Generate the Docker artifact, initialize a Fly application from that directory, and configure the internal port as `3000`. ```sh fh build --target docker cd .fabricharness/build/docker fly launch --no-deploy fly secrets set FABRIC_HARNESS_API_TOKEN=... FABRIC_MODEL=... fly deploy ``` Use managed or external Postgres for durable state; do not rely on an ephemeral root filesystem. Restrict public routes, configure health/readiness checks, pin regions and machine resources, and test process restart plus rolling deployment behavior. --- # Foundry Hosted Agents (Azure) Canonical: https://harness.fabric.pro/docs/deployment/foundry-hosted-agent Build scaffolds and invoke Azure AI Foundry Agent Service from Fabric Harness. Foundry Hosted Agents are Fabric Harness's primary Azure direction. Fabric supports two related paths: 1. **Build scaffold** — `fh build --target foundry-hosted-agent` emits Docker/azd/Bicep/metadata scaffolding. 2. **Agent Service client/tools** — `@fabric-harness/azure` can invoke and manage Azure AI Foundry Agent Service agents from Fabric agents. Use the build scaffold when packaging a Fabric Node or Temporal worker for Azure-managed compute. Use the Agent Service client when an existing Fabric agent needs to invoke or administer an Azure AI Foundry agent. ## Build scaffold ```sh fh build --target foundry-hosted-agent ``` Output: ```txt .fabricharness/build/foundry-hosted-agent/ Dockerfile azure.yaml foundry-agent.yaml infra/main.bicep dist/server.mjs manifest.json ``` Build runtime variants: ```sh fh build --target foundry-hosted-agent --runtime node fh build --target foundry-hosted-agent --runtime temporal-worker ``` Deploy the scaffold with Azure Developer CLI: ```sh cd .fabricharness/build/foundry-hosted-agent azd up ``` ## Foundry Agent Service client ```ts import { createFoundryAgentServiceClient, foundryAgentTool, } from '@fabric-harness/azure'; const foundry = createFoundryAgentServiceClient({ projectEndpoint: process.env.AZURE_FOUNDRY_PROJECT_ENDPOINT!, agentId: process.env.AZURE_FOUNDRY_AGENT_ID!, token: process.env.AZURE_TOKEN!, }); const fabric = await init({ tools: [foundryAgentTool(foundry)], }); ``` This exposes a Fabric tool named `foundry_agent` with input: ```ts { prompt: string; threadId?: string; } ``` The client performs: 1. create or reuse a thread; 2. add a user message; 3. create a run for the configured agent; 4. optionally poll; 5. return thread/run status and messages. ## Lifecycle tools ```ts import { foundryAgentLifecycleTools } from '@fabric-harness/azure'; const tools = [ ...foundryAgentLifecycleTools(foundry), ]; ``` This exposes: - `foundry_create_agent` - `foundry_update_agent` - `foundry_delete_agent` These are privileged write effects. Gate them with approvals: ```ts const policy = { toolPolicy: { requireApproval: [ 'foundry_create_agent', 'foundry_update_agent', 'foundry_delete_agent', ], }, }; ``` ## Identity and audit model Fabric sessions can record actor identity: ```ts await init({ actor: { agentId: process.env.AZURE_ENTRA_AGENT_ID, onBehalfOf: userId, }, }); ``` Approval entries should record: - the Foundry/Entra agent identity; - the on-behalf-of user; - the approver; - the tool or command being authorized. ## Architecture guidance | Pattern | Use when | |---|---| | Fabric Temporal worker outside Foundry | You need durable replay, retries, and long approval waits. | | Fabric agent invoking Foundry Agent Service | You already have Foundry agents and want Fabric governance/tools around them. | ## Model provider On Azure compute with workload identity, route inference through `FoundryRuntimeModelProvider` instead of configuring `AZURE_OPENAI_API_KEY`: ```ts import { FoundryRuntimeModelProvider } from '@fabric-harness/azure/foundry-runtime'; import { init } from '@fabric-harness/sdk'; const fabric = await init({ modelProvider: new FoundryRuntimeModelProvider({ defaultModel: 'gpt-4o', }), }); ``` Set `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_DEPLOYMENT`; supply `FOUNDRY_AGENT_TOKEN` when your host injects a token. On ACA, AKS, or a VM with managed identity, install `@azure/identity` and the provider falls back to `DefaultAzureCredential`. See [model providers → Foundry runtime](/docs/building/model-providers#foundry-runtime-azure). ## Live test ```sh FABRIC_AZURE_FOUNDRY_TEST=1 \ AZURE_FOUNDRY_PROJECT_ENDPOINT=... \ AZURE_FOUNDRY_AGENT_ID=... \ AZURE_TOKEN=... \ pnpm --filter @fabric-harness/azure test ``` The live test retrieves the configured agent and invokes it with a smoke prompt. Set `FABRIC_AZURE_FOUNDRY_POLL=0` to create the run without polling for completion. ## See also - [Azure overview](/docs/deployment/azure) - [Live tests](/docs/reference/live-tests) - [Capability matrix](/docs/reference/capability-matrix) --- # GitHub Actions Canonical: https://harness.fabric.pro/docs/deployment/github-actions Build, sign, and deploy from GitHub Actions. A reference workflow that builds the workspace, emits a signed Docker image, and pushes to a registry. ```yaml name: build-agents on: push: branches: [main] jobs: build: runs-on: ubuntu-latest permissions: contents: read id-token: write packages: write steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: 10.10.0 - uses: actions/setup-node@v4 with: node-version: 22 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm build - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build, sign, push agents image env: COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} run: | ./packages/cli/dist/bin/fabric-harness.js build \ --target docker \ --docker-build --docker-push \ --docker-tag ghcr.io/${{ github.repository }}/agents:${{ github.sha }} \ --image-sbom --image-sbom-required \ --provenance --sign-provenance --signing-key env://COSIGN_PRIVATE_KEY \ --attestation - uses: actions/upload-artifact@v4 with: name: build-manifest path: | .fabricharness/build/docker/manifest.json .fabricharness/build/docker/provenance.json .fabricharness/build/docker/attestation.intoto.jsonl ``` ## CI smoke tests Add a job that runs the agent against the mock model in CI: ```yaml smoke: needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: version: 10.10.0 - uses: actions/setup-node@v4 with: node-version: 22 cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm build - run: | cd examples/issue-triage-ci ../../packages/cli/dist/bin/fabric-harness.js doctor --tools ../../packages/cli/dist/bin/fabric-harness.js run triage \ --model openai/gpt-5.5 \ --payload-file fixtures/issue.json ``` See [`examples/issue-triage-ci`](https://github.com/) for a complete read-only triage pipeline. --- # GitLab CI Canonical: https://harness.fabric.pro/docs/deployment/gitlab-ci Build, sign, and deploy from GitLab CI. A reference `.gitlab-ci.yml` that mirrors the GitHub Actions pipeline. ```yaml stages: - install - build - smoke - deploy variables: PNPM_VERSION: "10.10.0" install: stage: install image: node:22 script: - npm install -g pnpm@${PNPM_VERSION} - pnpm install --frozen-lockfile cache: key: files: - pnpm-lock.yaml paths: - node_modules - .pnpm-store build: stage: build image: node:22 needs: [install] script: - npm install -g pnpm@${PNPM_VERSION} - pnpm install --offline - pnpm build - ./packages/cli/dist/bin/fabric-harness.js build --target docker artifacts: paths: - .fabricharness/build/docker/ smoke: stage: smoke image: node:22 needs: [build] script: - npm install -g pnpm@${PNPM_VERSION} - pnpm install --offline - cd examples/issue-triage-ci - ../../packages/cli/dist/bin/fabric-harness.js run triage \ --model openai/gpt-5.5 \ --payload-file fixtures/issue.json publish: stage: deploy image: docker:27 services: - docker:27-dind needs: [build] rules: - if: '$CI_COMMIT_BRANCH == "main"' script: - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY" - cd .fabricharness/build/docker - docker build -t "$CI_REGISTRY_IMAGE/agents:$CI_COMMIT_SHA" . - docker push "$CI_REGISTRY_IMAGE/agents:$CI_COMMIT_SHA" ``` The same provenance and attestation flags work — wire `COSIGN_PRIVATE_KEY` into the GitLab masked variables and add the appropriate flags to the `build` stage. --- # Modal Canonical: https://harness.fabric.pro/docs/deployment/modal Run serverless GPU and CPU tasks from Fabric sessions through Modal's native TypeScript SDK. Fabric Harness runs its Node runtime in your application and sends sandbox work to a native Modal `Sandbox`. The adapter never serializes the Modal client or its credentials into session history. ## Install ```sh pnpm add @fabric-harness/connectors modal ``` ## Connect a sandbox ```ts import { ModalClient } from 'modal'; import { modalSdkSandbox } from '@fabric-harness/connectors/modal'; const client = new ModalClient(); const app = await client.apps.fromName('fabric-harness', { createIfMissing: true }); const image = client.images.fromRegistry('node:22-alpine'); const remote = await client.sandboxes.create(app, image, { timeoutMs: 300_000, workdir: '/workspace', // gpu: 'A10G', // outboundDomainAllowlist: ['api.example.internal'], }); const sandbox = modalSdkSandbox(remote, { cleanup: true }); try { const fabric = await init({ sandbox }); const session = await fabric.session(); await session.prompt('Run the compute task and return the generated artifact.'); } finally { await sandbox.cleanup(); client.close(); } ``` `modalSdkSandbox()` supports streamed stdout/stderr, exact binary files, cwd/env, timeout and abort settlement, portable `sandboxId` references, reconnect, and verified termination. ## Reconnect after worker rotation ```ts registerStandardSandboxRefDecoders({ modal: { connect: ({ sandboxId }) => client.sandboxes.fromId(sandboxId!), }, }); ``` Persist the result of `session.sandboxRef({ portable: true })`; it contains the sandbox ID and Fabric capability metadata, never Modal tokens. ## Environment variables - `MODAL_TOKEN_ID`: Modal token ID. - `MODAL_TOKEN_SECRET`: Modal token secret. - `MODAL_APP_NAME`: optional application name used by the runnable example. - `MODAL_IMAGE`: optional container image used by the runnable example. Run the credentialed contract with: ```sh FABRIC_MODAL_TEST=1 MODAL_TOKEN_ID=... MODAL_TOKEN_SECRET=... \ pnpm --filter @fabric-harness/connectors test ``` ## See also - [Sandbox connectors](/docs/building/sandbox-connectors#modal) - [Private networking](/docs/operating/private-networking) - [Modal documentation](https://modal.com/docs) --- # Node Canonical: https://harness.fabric.pro/docs/deployment/node Build and run the shared v2 HTTP server as a self-contained Node artifact. The Node target is the simplest production artifact. It packages finite jobs, persistent agents, roles, skills, configuration, and the same HTTP server used by `fh dev`. ## Build ```sh fh build --target node ``` Output: ```text .fabricharness/build/node/ .fabricharness/ jobs/*.mjs agents/*.mjs roles/ skills/ dist/ jobs/*.mjs agents/*.mjs server.mjs manifest.json package.json ``` Definitions are bundled into `.mjs` modules so the runtime can discover them dynamically without depending on the source workspace. ## Run ```sh PORT=8080 OPENAI_API_KEY="$OPENAI_API_KEY" \ node .fabricharness/build/node/dist/server.mjs ``` The server binds `0.0.0.0:3000` by default for container compatibility. ## Invoke a finite job ```sh curl http://localhost:8080/jobs/ask \ -H 'content-type: application/json' \ -d '{"question":"What is durable execution?"}' ``` The response includes `{ result, runId, agentPath }`. ## Invoke a persistent agent ```sh curl -i http://localhost:8080/agents/support/customer-42 \ -H 'content-type: application/json' \ -d '{"message":"Help me understand my bill."}' ``` The default response is `202 { submissionId, streamUrl, offset }`. Add `?wait=true` only when a synchronous bridge is required. Prefer `@fabric-harness/client` for waiting and offset streaming. ## Core routes | Method | Route | Purpose | | --- | --- | --- | | `GET` | `/health` | Liveness probe. | | `GET` | `/ready` | Readiness and workspace root. | | `GET` | `/admin/agents` | List finite jobs and persistent agents with kind. | | `POST` | `/jobs/:name` | Invoke a finite job. | | `POST` | `/agents/:name/:id` | Admit persistent input; `202` by default. | | `POST` | `/agents/:name/:id/dispatch` | Enqueue asynchronous persistent input. | | `GET` | `/agents/:name/:id/conversation` | Read conversation batches by offset. | | `GET` | `/agents/:name/:id/stream?offset=...` | Catch up and tail with SSE. | | `GET` | `/agents/:name/:id/submissions/:submissionId` | Read settlement status. | | `POST` | `/agents/:name/:id/abort` | Request durable abort. | | `GET` | `/sessions` | List persisted sessions. | | `GET` | `/builds/:target/manifest` | Read a build manifest under the workspace build directory. | ## Environment | Variable | Purpose | | --- | --- | | `PORT` | Listen port; default `3000`. | | `DATABRICKS_APP_PORT` | Takes precedence over `PORT` in a generated App artifact. | | `FABRIC_HARNESS_HOST` | Listen address; default `0.0.0.0`. | | `FABRIC_HARNESS_API_TOKEN` | Bearer token for HTTP and WebSocket access; required for protected routes in production unless a custom authorizer is installed. | | `FABRIC_HARNESS_MAX_BODY_BYTES` | JSON/body limit; default 1 MiB. | | `FABRIC_HARNESS_RATE_LIMIT_MAX` | Per-process request limit; `0` disables it. | | `FABRIC_HARNESS_RATE_LIMIT_WINDOW_MS` | Rate-limit window; default 60 seconds. | | `FABRIC_HARNESS_TENANT_REQUIRED=1` | Require `X-Fabric-Tenant`. | ## Production exposure When `NODE_ENV=production` or `FABRIC_ENV=production`, public job and agent routes require `triggers.webhook: true`. Authentication and triggers serve different purposes: the bearer token identifies an allowed caller, while the trigger opts a definition into public HTTP exposure. ```sh NODE_ENV=production \ FABRIC_HARNESS_API_TOKEN="$API_TOKEN" \ node dist/server.mjs ``` ## Container wrapper Copy the complete build directory, not only `dist/`; the embedded workspace contains discoverable definitions, roles, skills, and runtime configuration. ```dockerfile FROM node:22-slim WORKDIR /app COPY . . ENV NODE_ENV=production PORT=8080 EXPOSE 8080 CMD ["node", "dist/server.mjs"] ``` For a generated container and optional image build, use the [Docker target](/docs/deployment/docker). For the full job/agent walkthrough, see [Build and run artifacts](/docs/deployment/build-artifacts). --- # Railway Canonical: https://harness.fabric.pro/docs/deployment/railway Deploy a Fabric Docker artifact on Railway. Point a Railway service at the Docker build artifact or repository Dockerfile and expose port `3000`. Set `NODE_ENV=production`, `FABRIC_HARNESS_API_TOKEN`, model credentials, and `DATABASE_URL` through Railway variables. Use Postgres for durable sessions/submissions and object storage for large artifacts. Configure `/health` and `/ready`, resource limits, private service networking, and restart/redeploy recovery checks. --- # Render Canonical: https://harness.fabric.pro/docs/deployment/render Deploy a Fabric Harness HTTP server to Render as a Web Service via Blueprint. Render is a one-click PaaS that runs containers (Web Services), Cron Jobs, and Postgres. The Fabric Harness Render target emits a Blueprint (`render.yaml`) plus a Dockerfile so you can deploy with no Render-specific code. ## Quickstart ```sh fabric-harness build --target render # Output: .fabricharness/build/render/{Dockerfile, render.yaml, RENDER_DEPLOY.md, dist/, ...} ``` Push the bundle to a Git repo, then create a new Web Service on Render pointing at it. Render auto-detects `render.yaml` and provisions the service. ## What ends up in the bundle | File | Purpose | |---|---| | `Dockerfile` | Standard Node 22 container image. | | `render.yaml` | Blueprint declaring a `runtime: docker` Web Service with `/health` healthcheck. | | `RENDER_DEPLOY.md` | Per-bundle deployment notes (env vars, Postgres setup). | | `dist/server.mjs` | The Fabric Harness HTTP server entry point. | ## Required environment variables Set these in the Render dashboard (or via Blueprint env groups): - `OPENAI_API_KEY` (or other provider keys, depending on your model config) - `DATABASE_URL` — Postgres DSN (Render's managed Postgres works directly) - `FABRIC_HARNESS_SESSION_STORE=postgres` ## Why Postgres for the session store? Render restarts containers and resets the local disk on every redeploy. The default `FileSessionStore` will lose state. Provision a Render Postgres instance and use `PostgresSessionStore` (selected via `FABRIC_HARNESS_SESSION_STORE=postgres` + `DATABASE_URL`). For session histories you can afford to lose (e.g. one-shot CI agents), `InMemorySessionStore` is fine — no Postgres required. ## Health check Render probes `/health` by default; the Fabric Harness HTTP server exposes that route automatically. No code changes needed. ## When to use Render vs other targets | Pick Render when… | Pick another target when… | |---|---| | You want one-click deploys without managing Kubernetes / containers | You need durable workflows → use `--target temporal-worker` | | Your agent is HTTP-shaped (webhook, REST) | Your agent runs on the edge → use `--target cloudflare` | | You want managed Postgres alongside the app | You're on Azure / AWS / GCP → use `--target aca` / `--target aks` / Docker on ECS | ## Cost sanity Render's free tier sleeps after 15 minutes of inactivity. For production, use Starter (always-on) or higher. Per-call cost telemetry shows up in `fh metrics --breakdown` so you can size your plan correctly. ## See also - [Docker target](/docs/deployment/docker) - [Cost telemetry](/docs/building/model-providers#per-call-cost-telemetry) --- # SST Canonical: https://harness.fabric.pro/docs/deployment/sst Deploy Fabric Node or Docker artifacts with SST-managed AWS infrastructure. Use SST to provision an AWS container service around the Fabric Docker target; do not place the long-running HTTP server in a short-lived function without redesigning durable admission and streaming. Build with `fh build --target docker`, publish the immutable image, and pass secrets through SST/AWS secret bindings. Use VPC networking, task roles, external Postgres, explicit autoscaling and health checks, authenticated production routes, and OpenTelemetry export. Verify that rolling replacement preserves submissions and conversation streams. --- # Temporal Worker Canonical: https://harness.fabric.pro/docs/deployment/temporal-worker Run agent sessions as durable Temporal workflows. The Temporal worker target turns sessions into durable Temporal workflows. Prompts, skills, tasks, shell calls, checkpoints, approvals, and cancellation all route through the `SessionRuntime` boundary; deterministic workflow code orchestrates state, and nondeterministic work happens in activities. Use this target for long-running jobs, approval delays, and restart durability. Before rollout, validate namespace authentication, gRPC readiness, worker restart, workflow replay, activity retry, and approval resumption against the Temporal environment you operate. ## Build the worker artifact ```sh fh build --target temporal-worker ``` Output: ``` .fabricharness/build/temporal-worker/ dist/worker.mjs manifest.json README.temporal-worker.md ``` ## Run the worker You can run the prebuilt artifact: ```sh node .fabricharness/build/temporal-worker/dist/worker.mjs ``` Or run a worker against the live workspace (great for development): ```sh fh temporal-worker --task-queue fabric-harness --address localhost:7233 ``` ## Drive an agent on the worker ```sh fh run ask --target temporal-worker --id ask-001 --prompt "What is Temporal?" ``` The CLI: 1. Connects to Temporal, 2. Starts (or resumes) the session workflow with id `ask-001`, 3. Signals it with the prompt, 4. Streams output back. ## Workflow model ```mermaid graph LR A[Session workflow] --> P[Prompt/skill/task request] A --> M[Model activity] A --> T[Tool activity] A --> S[Shell activity] A --> C[Checkpoint activity] A --> W{Approval needed?} W -->|yes| SIG[Wait for approval signal] SIG --> T W -->|no| R[Append result] ``` ## Supported runtime operations | Capability | Temporal path | | --- | --- | | `session.prompt()` | Durable workflow request + model/tool activities | | `session.skill()` | Same prompt runtime with skill-loaded instructions | | `session.task()` | Durable task request; child-workflow shape where configured | | `session.shell()` | Shell activity through the selected sandbox | | Checkpoint create/restore | Activity-backed when the sandbox reports support | | Approvals | Workflow waits on approval signals; no worker thread is held | | Cancellation | Workflow/activity cancellation is propagated where provider APIs support it | | Worker restart | Workflow state survives; activities are idempotency-keyed | | Attachments | Stored through the configured durable attachment/session store and remain addressable across worker rotation | | Tenant/cost/audit events | Model-attempt entries include usage and cost; tenant and operation records remain store-backed | Unsupported inline-only features should fail with explicit runtime errors rather than silently degrading. Cancel from the same `AbortSignal` used by the lightweight runtime. Fabric removes the signal and callbacks before serializing workflow input, then cancels the owning workflow when the signal fires: ```ts const controller = new AbortController(); const pending = session.prompt('Build the report', { signal: controller.signal }); controller.abort(new DOMException('Operator cancelled', 'AbortError')); await pending; // rejects with AbortError ``` ## Configuration `.fabricharness/config.ts`: ```ts export default { temporal: { address: 'localhost:7233', taskQueue: 'fabric-harness', namespace: 'default', workflowIdPrefix: 'fabric', promptWorkflowMode: 'hybrid', apiKeyEnv: 'TEMPORAL_API_KEY', // for Temporal Cloud tls: { /* ... */ }, }, }; ``` Environment overrides: | Variable | Purpose | | --- | --- | | `FABRIC_TEMPORAL_ADDRESS` | host:port | | `FABRIC_TEMPORAL_TASK_QUEUE` | task queue name | | `FABRIC_TEMPORAL_NAMESPACE` | namespace | | `FABRIC_TEMPORAL_CONNECT_ATTEMPTS` | Optional bounded retry attempts for client/worker connection startup. | | `FABRIC_TEMPORAL_CONNECT_INITIAL_DELAY_MS` | Optional first retry delay in milliseconds. | | `FABRIC_TEMPORAL_CONNECT_MAX_DELAY_MS` | Optional max retry delay in milliseconds. | ## Temporal Cloud Set `address` to your Temporal Cloud endpoint, configure mTLS or API key, and the worker connects normally: ```ts export default { temporal: { address: 'my-namespace.tmprl.cloud:7233', namespace: 'my-namespace', apiKeyEnv: 'TEMPORAL_API_KEY', tls: { serverName: 'my-namespace.tmprl.cloud' }, }, }; ``` ## Operational notes - Workers are stateless. Run several behind your task queue for throughput. - Approval waits are signal-driven and consume no worker time. - Use Temporal's UI or `temporal workflow show` to inspect history independently of `fh inspect`. - Keep default unit/mock Temporal tests in main CI and run the live conformance suite on the scheduled workflow. It covers prompt, skill, child task, shell, checkpoint, tool and custom approvals, cancellation, activity retry, attachments, usage/cost, and worker restart. For local `temporalio/auto-setup` with Postgres, set `DB_PORT=5432` and wait for a real gRPC connection rather than only checking the TCP port. - For production, test worker restart while a workflow is waiting for approval, retry behavior for failed activities, namespace/auth configuration, and cancellation of long shell/model work. --- # Ecosystem Canonical: https://harness.fabric.pro/docs/ecosystem Tools that extend Fabric Harness development, evaluation, and operations. Fabric Harness integrations use normal TypeScript packages and the same job, agent, session, sandbox, and eval APIs as the core framework. This catalog lists integrations backed by current code, a tested structural adapter, or an explicit project-local recipe. ## Catalog | Category | First-party surface | Guides | | --- | --- | --- | | [Channels](/docs/ecosystem/channels) | 17 signed first-party adapters across chat, support, commerce, billing, knowledge, and email. | Provider setup and outbound tools. | | [Sandboxes](/docs/ecosystem/sandboxes) | Databricks SQL, Cloudflare, Daytona, E2B, Modal, Vercel, Kubernetes, and the generic remote adapter. | SQL, code, edge, and remote sandbox guides. | | [Databases](/docs/ecosystem/databases) | Postgres/Lakebase stores plus Postgres, MySQL, MongoDB, Redis, and SQLite governed tools. | libSQL, Supabase, Turso, and Valkey guides. | | [Tooling](/docs/ecosystem/tooling) | Fabric evals and OpenTelemetry hooks. | Braintrust, Jetty, OpenTelemetry, Sentry, and Vitest evals. | | [Deployment](/docs/deployment) | Node, Docker, Cloudflare, Azure, Databricks, Render, Kubernetes, and Temporal targets. | Deployment catalog | ## Tooling - [Jetty](/docs/ecosystem/tooling/jetty) grades job output with a separately deployed rubric and stores comparable trajectories. - [Fabric evals](/docs/building/evals) runs deterministic and model-judged quality gates locally or in CI. ## Add integrations `fh add` exposes two explicit modes: ```sh fh add # browse the full recipe catalog fh add --json # machine-readable catalog fh add channel slack # scaffold files directly fh add daytona --print # print a guide for a coding agent fh add daytona | codex # apply the guide with Codex fh add discord --install-deps | codex # install declared dependencies, then apply the guide ``` A scaffold recipe writes files and tests and installs missing dependencies through the detected package manager. `--no-install` updates the manifest only. A connector guide is Markdown for adapting a provider SDK to the current project; `--install-deps` installs its cataloged dependencies before emitting the guide. The catalog labels each entry so these paths are not confused. Keep external credentials in environment variables or secret managers. Do not place them in prompts, job inputs, session history, or uploaded evaluation files. --- # Discord Canonical: https://harness.fabric.pro/docs/ecosystem/channels/discord Dispatch verified Discord interactions and messages to persistent agents. Use the adapter from `@fabric-harness/channels/discord`. Scaffold it with: ```sh fh add channel discord ``` ```ts import { createDiscordChannel } from '@fabric-harness/channels/discord'; export default createDiscordChannel({ applicationId: process.env.DISCORD_APPLICATION_ID!, publicKey: process.env.DISCORD_PUBLIC_KEY!, agent: 'assistant', }); ``` Mount the generated route as `POST /channels/discord/interactions`. The adapter verifies Discord's Ed25519 signature over the timestamp and exact body, answers interaction pings, deduplicates by interaction ID, uses the guild as tenant, records the Discord user as `onBehalfOf`, and keys the persistent instance by application/guild/channel/thread. Bind `replyInDiscord(ref, { botToken })` as a write-effect tool for outbound messages. Keep moderation actions in separate approval-gated tools. Configure the Discord interaction URL to `/channels/discord/interactions` and validate OAuth scopes and the production bot installation before rollout. --- # GitHub Canonical: https://harness.fabric.pro/docs/ecosystem/channels/github Dispatch signed GitHub issue and pull-request events to a persistent agent. ```ts title=".fabricharness/channels/github.ts" import { createGitHubChannel } from '@fabric-harness/channels/github'; export const channel = createGitHubChannel({ secret: process.env.GITHUB_WEBHOOK_SECRET!, agent: 'triage', }); ``` The server mounts the channel at `POST /channels/github/webhook`. GitHub's `X-GitHub-Delivery` becomes the dispatch deduplication key, and the conversation key identifies the repository issue or pull request. Use `commentOnGitHubIssue(ref, { token })` as an outbound tool on the persistent agent. Keep the token in `GITHUB_TOKEN`, scope it to the repositories and write operations the agent needs, and require approval for comment or mutation tools through the agent capability policy. For read-oriented repository access instead of event ingress, use `githubSource()` from `@fabric-harness/connectors/github` or the `github-mcp` connector guide. --- # Google Chat Canonical: https://harness.fabric.pro/docs/ecosystem/channels/google-chat Route authenticated Google Chat events into persistent agent threads. ```sh fh add channel google-chat ``` ```ts title=".fabricharness/channels/google-chat.ts" import { createGoogleChatChannel } from '@fabric-harness/channels/google-chat'; export default createGoogleChatChannel({ audience: process.env.GOOGLE_CHAT_AUDIENCE!, agent: 'assistant', }); ``` Set the Chat app HTTP endpoint to `POST /channels/google-chat/webhook` and use the same endpoint URL or Cloud project number as `GOOGLE_CHAT_AUDIENCE`. The adapter verifies the Google Chat issuer, audience, signature, expiry, and not-before claims. Space is the default tenant, the user is the actor, and space/thread is the persistent key. Use `replyInGoogleChat(ref, { accessToken, space, thread })` for governed asynchronous replies. Use minimal Chat scopes and live-test service-account or delegated credentials in the target Workspace domain. See [Google request verification](https://developers.google.com/workspace/chat/verify-requests-from-chat). --- # Intercom Canonical: https://harness.fabric.pro/docs/ecosystem/channels/intercom Dispatch verified Intercom conversation events to support agents. ```sh fh add channel intercom ``` ```ts import { createIntercomChannel } from '@fabric-harness/channels/intercom'; export default createIntercomChannel({ clientSecret: process.env.INTERCOM_CLIENT_SECRET!, agent: 'support', }); ``` Configure the Developer Hub webhook URL as `POST /channels/intercom/webhook`. The adapter verifies Intercom's `X-Hub-Signature` HMAC, deduplicates notification IDs, propagates contact identity, and keys persistent sessions by conversation or ticket. Use `replyInIntercom(ref, { accessToken, conversationId, adminId })` for governed replies. Apply customer-data redaction and retention policy before placing conversation content in model context or telemetry. --- # Linear Canonical: https://harness.fabric.pro/docs/ecosystem/channels/linear Connect Linear issue events and governed issue tools. ```sh fh add channel linear ``` ```ts import { createLinearChannel } from '@fabric-harness/channels/linear'; export default createLinearChannel({ signingSecret: process.env.LINEAR_WEBHOOK_SECRET!, agent: 'triage', }); ``` Register `POST /channels/linear/webhook` in the Linear integration. The adapter verifies `Linear-Signature` over the exact body, uses the webhook ID for deduplication, propagates the organization and actor, and keys the session by Linear object ID. Use `commentOnLinearIssue(ref, { apiKey, issueId })` for a governed GraphQL comment tool. Require approval for issue creation, state changes, comments, and destructive mutations according to the workload. --- # Facebook Messenger Canonical: https://harness.fabric.pro/docs/ecosystem/channels/messenger Connect Meta Messenger webhooks to persistent Fabric agents. ```sh fh add channel messenger ``` ```ts import { createMessengerChannel } from '@fabric-harness/channels/messenger'; export default createMessengerChannel({ appSecret: process.env.MESSENGER_APP_SECRET!, verifyToken: process.env.MESSENGER_VERIFY_TOKEN!, agent: 'support', }); ``` Configure both `GET` and `POST /channels/messenger/webhook`. GET answers Meta's verification challenge; POST verifies `X-Hub-Signature-256`, uses message IDs for deduplication, propagates Page and sender identity, and suppresses Page echo events. Use `replyInMessenger(ref, { pageAccessToken })` for a governed Send API tool. Keep Page tokens host-side and live-test webhook subscription, permissions, token rotation, retries, and deletion/privacy handling. --- # Notion Canonical: https://harness.fabric.pro/docs/ecosystem/channels/notion Handle Notion integration events with page-scoped access. ```sh fh add channel notion ``` ```ts import { createNotionChannel } from '@fabric-harness/channels/notion'; export default createNotionChannel({ verificationToken: process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN!, agent: 'knowledge', onVerificationToken: (token) => console.info('Store the Notion verification token:', token), }); ``` Create a webhook subscription for `POST /channels/notion/webhook`, capture its one-time `verification_token` through `onVerificationToken`, and store it in the environment. The adapter answers verification requests, then verifies `X-Notion-Signature`, deduplicates by event ID, and keys sessions by workspace/entity. Use `commentOnNotionPage(ref, { token, parentId })` for governed comments. Restrict the integration to explicitly shared pages and databases. See [Notion webhooks](https://developers.notion.com/reference/webhooks). --- # Resend Canonical: https://harness.fabric.pro/docs/ecosystem/channels/resend Process verified inbound and delivery email events. ```sh fh add channel resend ``` ```ts import { createResendChannel } from '@fabric-harness/channels/resend'; export default createResendChannel({ signingSecret: process.env.RESEND_WEBHOOK_SECRET!, tenantId: 'acme', agent: 'mail', }); ``` Register `POST /channels/resend/webhook`. The adapter verifies the Svix ID, timestamp, and signature against exact bytes, rejects stale deliveries, deduplicates by `svix-id`, and keys the session by email ID. Use `sendWithResend(ref, { apiKey, from, to })` for governed outbound email. Redact sensitive message bodies and attachments before model, telemetry, or evaluation capture, and live-test domain configuration and delivery retries. --- # Salesforce Marketing Cloud Canonical: https://harness.fabric.pro/docs/ecosystem/channels/salesforce-marketing-cloud Connect Marketing Cloud events with governed campaign actions. ```sh fh add channel salesforce-marketing-cloud ``` ```ts import { createSalesforceMarketingCloudChannel } from '@fabric-harness/channels/salesforce-marketing-cloud'; export default createSalesforceMarketingCloudChannel({ signatureKey: process.env.SFMC_ENS_SIGNATURE_KEY!, agent: 'engagement', }); ``` Register and verify the ENS callback at `POST /channels/salesforce-marketing-cloud/webhook`, then store the callback's signature key. The adapter verifies `x-sfmc-ens-signature` over the complete batch, expands events into durable dispatches, propagates MID/business-unit tenancy, and deduplicates by composite ID, event type, and timestamp. Use `sendMarketingCloudMessage(ref, { restBaseUrl, accessToken, definitionKey, recipient })` for a governed transactional send. Treat sends, journey changes, subscriber mutations, and data-extension writes as high-impact operations requiring least-privilege OAuth, audit, and approval. --- # Shopify Canonical: https://harness.fabric.pro/docs/ecosystem/channels/shopify Process signed Shopify commerce webhooks with governed actions. ```sh fh add channel shopify ``` ```ts import { createShopifyChannel } from '@fabric-harness/channels/shopify'; export default createShopifyChannel({ clientSecret: process.env.SHOPIFY_CLIENT_SECRET!, agent: 'commerce', }); ``` Subscribe the app to `POST /channels/shopify/webhook`. The adapter verifies `X-Shopify-Hmac-Sha256`, uses `X-Shopify-Webhook-Id` for deduplication, records the delivered API version, propagates shop identity, and keys sessions by the GraphQL resource ID. Use `updateShopifyOrderNote(ref, { shop, accessToken, orderId, apiVersion })` for governed writes. Refund, fulfillment, inventory, customer, and order writes require narrow scopes, idempotency, policy, and usually human approval. --- # Slack Canonical: https://harness.fabric.pro/docs/ecosystem/channels/slack Dispatch signed Slack events to a persistent agent and reply in-thread. Create the channel: ```ts title=".fabricharness/channels/slack.ts" import { createSlackChannel } from '@fabric-harness/channels/slack'; export const channel = createSlackChannel({ signingSecret: process.env.SLACK_SIGNING_SECRET!, agent: 'assistant', }); ``` Bind the outbound tool to the addressed Slack thread: ```ts title=".fabricharness/agents/assistant.ts" import { defineAgent } from '@fabric-harness/sdk'; import { parseSlackConversationKey, replyInSlackThread } from '@fabric-harness/channels/slack'; export default defineAgent(({ id }) => ({ instructions: 'Answer the Slack thread concisely.', tools: [replyInSlackThread(parseSlackConversationKey(id), { botToken: process.env.SLACK_BOT_TOKEN!, })], })); ``` Configure the Slack Event Subscriptions request URL as `/channels/slack/events` and subscribe to `app_mention` or threaded message events. The adapter verifies Slack's signature, uses `event_id` for deduplication, and keys the persistent instance by thread. Required secrets are `SLACK_SIGNING_SECRET` and `SLACK_BOT_TOKEN`. The complete project is under `examples/with-slack-channel`. --- # Stripe Canonical: https://harness.fabric.pro/docs/ecosystem/channels/stripe Route signed Stripe events with financial-operation controls. ```sh fh add channel stripe ``` ```ts import { createStripeChannel } from '@fabric-harness/channels/stripe'; export default createStripeChannel({ endpointSecret: process.env.STRIPE_WEBHOOK_SECRET!, agent: 'billing', }); ``` Create an event destination for `POST /channels/stripe/webhook`. The adapter verifies every `Stripe-Signature` over exact bytes, applies the standard five-minute replay window, deduplicates by event ID, and uses the connected account and customer/resource as tenant and session identity. Use `updateStripeCustomer(ref, { secretKey, customerId })` as a narrowly scoped governed tool. Never let model output directly choose unrestricted amounts, recipients, or idempotency keys. Refunds, transfers, subscription changes, and other financial writes should require approval. --- # Microsoft Teams Canonical: https://harness.fabric.pro/docs/ecosystem/channels/teams Route validated Bot Framework activities to persistent agents. Use the adapter from `@fabric-harness/channels/teams`. The direct recipe configures the built-in Bot Connector JWT verifier: ```sh fh add channel teams ``` ```ts import { createTeamsChannel } from '@fabric-harness/channels/teams'; export default createTeamsChannel({ agent: 'assistant', applicationId: process.env.TEAMS_APP_ID!, }); ``` The adapter discovers Microsoft Bot Connector signing keys and validates the RS256 signature, issuer, App ID audience, token lifetime, activity `serviceUrl`, and channel endorsement. Key metadata is cached for one hour and refreshed once when an unknown key ID appears. Accepted message activities use the Azure tenant as `tenantId`, the AAD/Bot Framework user as `onBehalfOf`, the activity ID for deduplication, and tenant/conversation/reply IDs for the stable persistent instance. Use `authenticate` instead of `applicationId` only when an existing identity gateway performs the same validations before the request reaches Fabric Harness. Sovereign-cloud deployments can set `botFramework.openIdMetadataUrl` to their Bot Connector metadata endpoint. Use `replyInTeamsConversation(ref, { accessToken })` for outbound Bot Framework replies. The access token resolver can rotate credentials per call. Mount the route at `POST /channels/teams/activities`. Prefer managed identity where supported, restrict Graph/Bot scopes, and policy-gate Graph mutations separately from message replies. --- # Telegram Canonical: https://harness.fabric.pro/docs/ecosystem/channels/telegram Connect Telegram bot updates to durable agent conversations. Use the adapter from `@fabric-harness/channels/telegram`: ```sh fh add channel telegram ``` ```ts import { createTelegramChannel } from '@fabric-harness/channels/telegram'; export default createTelegramChannel({ botId: process.env.TELEGRAM_BOT_ID!, botToken: process.env.TELEGRAM_BOT_TOKEN!, secretToken: process.env.TELEGRAM_WEBHOOK_SECRET!, agent: 'assistant', tenantId: 'acme', }); ``` Telegram must send updates to `POST /channels/telegram/webhook` with the configured `X-Telegram-Bot-Api-Secret-Token`. The adapter deduplicates by update ID, keys sessions by bot/chat/topic, and records the sender as `onBehalfOf`. Use `replyInTelegram(ref, { botToken })` for outbound messages. Keep edit, delete, and administration actions as separately governed tools. --- # Twilio Canonical: https://harness.fabric.pro/docs/ecosystem/channels/twilio Connect messaging and voice events to Fabric agents. Use the messaging adapter from `@fabric-harness/channels/twilio`: ```sh fh add channel twilio ``` ```ts import { createTwilioChannel } from '@fabric-harness/channels/twilio'; export default createTwilioChannel({ accountSid: process.env.TWILIO_ACCOUNT_SID!, authToken: process.env.TWILIO_AUTH_TOKEN!, webhookUrl: process.env.TWILIO_WEBHOOK_URL!, agent: 'assistant', tenantId: 'acme', }); ``` Mount `POST /channels/twilio/messages`. `webhookUrl` must be the exact public URL configured in Twilio when a proxy rewrites the internal request URL. The adapter validates `X-Twilio-Signature` with HMAC-SHA1 over the URL and sorted form parameters, deduplicates by Message SID, records the sender as actor, and distinguishes SMS from `whatsapp:` addresses. Use `replyWithTwilio(ref, { authToken })` for outbound SMS or Twilio WhatsApp messages. For Media Streams also follow the voice-telephony connector specification. Gate outbound calls, transfers, and recordings separately and apply phone-number/recording privacy controls. --- # WhatsApp Canonical: https://harness.fabric.pro/docs/ecosystem/channels/whatsapp Route verified WhatsApp Cloud API webhooks to persistent agents. Use the Meta Cloud API adapter from `@fabric-harness/channels/whatsapp`: ```sh fh add channel whatsapp ``` ```ts import { createWhatsAppChannel } from '@fabric-harness/channels/whatsapp'; export default createWhatsAppChannel({ appSecret: process.env.WHATSAPP_APP_SECRET!, verifyToken: process.env.WHATSAPP_VERIFY_TOKEN!, agent: 'assistant', }); ``` Configure both `GET` and `POST /channels/whatsapp/webhook`. The GET route completes Meta's verification challenge. The POST route verifies `X-Hub-Signature-256` against the exact body, deduplicates by WhatsApp message ID, uses the business account as tenant, records the sender as `onBehalfOf`, and keys the persistent instance by business account/phone number/sender. Use `replyInWhatsApp(ref, { accessToken })` for text replies. Keep template credentials host-side, govern template sends separately, and live-test phone-number/business permissions before rollout. --- # Zendesk Canonical: https://harness.fabric.pro/docs/ecosystem/channels/zendesk Dispatch verified ticket events to persistent support agents. ```sh fh add channel zendesk ``` ```ts import { createZendeskChannel } from '@fabric-harness/channels/zendesk'; export default createZendeskChannel({ signingSecret: process.env.ZENDESK_WEBHOOK_SECRET!, subdomain: process.env.ZENDESK_SUBDOMAIN!, agent: 'support', }); ``` Point a Zendesk webhook at `POST /channels/zendesk/webhook`. The adapter signs the timestamp plus exact body, verifies the base64 signature, deduplicates deliveries, and keys sessions by ticket. Use `commentOnZendeskTicket(ref, { subdomain, email, token, ticketId })` for governed public or private comments. Redact attachments and personal data as required, and govern comments, status, assignment, and deletion tools independently. --- # libSQL Canonical: https://harness.fabric.pro/docs/ecosystem/databases/libsql Run the unified Fabric store on local libSQL or a remote compatible service. ```sh pnpm add @fabric-harness/node @libsql/client ``` ```ts title=".fabricharness/config.ts" import { createClient } from '@libsql/client'; import { libsqlPersistence } from '@fabric-harness/node'; export default function config() { const client = createClient({ url: 'file:.fabricharness/fabric.db' }); return { persistence: libsqlPersistence({ client }) }; } ``` The adapter provides the complete bundle and uses version-fenced updates rather than relying on a long interactive transaction. That keeps the same code compatible with local libSQL and remote HTTP clients. Mutations within one persistence surface are atomic; cascade deletion spans several surface rows and is intentionally not one remote transaction. Retry deletion after transport failure, or use Postgres when cross-surface transactionality is mandatory. For model-facing data, run `fh add libsql`. The managed recipe installs `@libsql/client`, creates a fixed-statement governed lookup tool and test, and prints its verification command. Parameterize every value, cap rows and duration, separate reads and writes, propagate tenant filters, and require approval for writes or DDL. --- # MongoDB Canonical: https://harness.fabric.pro/docs/ecosystem/databases/mongodb Expose collection-scoped MongoDB operations to agents. ## Runtime persistence ```sh pnpm add @fabric-harness/node mongodb ``` ```ts title=".fabricharness/config.ts" import { mongodbPersistence } from '@fabric-harness/node'; import { MongoClient } from 'mongodb'; export default async function config() { const client = new MongoClient(process.env.MONGODB_URL!); await client.connect(); const collection = client.db('fabric').collection('fabric_harness_snapshots'); return { persistence: mongodbPersistence({ collection, client }) }; } ``` The bundle uses `_id` plus a monotonic version for atomic compare-and-swap, so it works on a standalone server and on replica sets without multi-document transactions. Use majority write concern and retryable writes for production. Payloads are canonical JSON strings, which preserves Fabric's `undefined` semantics and permits arbitrary tool-result keys without Mongo field-name interpretation. Each store surface is one snapshot document. This is appropriate for moderate operational state; choose Postgres for high-contention workloads or very large histories. ## Governed data tools Use the collection-bound MongoDB tool. Scaffold the official driver and adapter: ```sh fh add database mongodb ``` ```ts const lookup = mongoFindTool({ collection: db.collection('accounts'), collectionName: 'accounts', name: 'lookup_account', description: 'Read one tenant-scoped account.', filter: ({ tenantId, accountId }) => ({ tenantId, id: accountId }), projection: { _id: 0, id: 1, status: 1 }, maxRows: 1, }); ``` Host code constructs the filter and projection, so the model cannot submit an arbitrary query document. The adapter applies result/time/byte limits and redacted errors. Writes, indexes, and administration should remain separate approval-gated tools. --- # MySQL Canonical: https://harness.fabric.pro/docs/ecosystem/databases/mysql Add least-privilege, parameterized MySQL tools. ## Runtime persistence ```sh pnpm add @fabric-harness/node mysql2 ``` ```ts title=".fabricharness/config.ts" import { mysqlPersistence } from '@fabric-harness/node'; import mysql from 'mysql2/promise'; export default function config() { const pool = mysql.createPool(process.env.MYSQL_URL!); return { persistence: mysqlPersistence({ client: pool }) }; } ``` `mysqlPersistence()` supplies sessions, submissions, conversation streams, binary attachments, runs/events, atomic cost budgets, health, migration, and cascade deletion. It stores one version-fenced JSON snapshot per persistence surface and uses compare-and-swap updates, so multiple replicas cannot both commit the same transition. Use MySQL 8/InnoDB, TLS, backups, and a `max_allowed_packet` larger than your largest attachment snapshot. Snapshot rows favor moderate agent operational state; use Postgres when workloads have very high write concurrency or histories large enough that rewriting a surface snapshot is costly. ## Governed data tools Use the fixed-statement MySQL tool. Scaffold `mysql2` and `@fabric-harness/databases/mysql` with: ```sh fh add database mysql ``` ```ts const lookup = mysqlTool({ client: pool, name: 'lookup_account', description: 'Read one tenant-scoped account.', statement: 'select id, status from accounts where tenant_id = ? and id = ? limit 1', parameters: ({ tenantId, accountId }) => [tenantId, accountId], }); ``` The application owns the SQL; model input only becomes bound parameters. Read tools reject mutations and multi-statements. Use a least-privilege user, keep `multipleStatements: false`, and mark controlled mutations `effect: 'write'` so approvals can gate them. Runtime credentials stay in host configuration and are never added to model-facing tools. --- # Postgres Canonical: https://harness.fabric.pro/docs/ecosystem/databases/postgres Persist Fabric session state and artifacts in Postgres. ```sh pnpm add @fabric-harness/node pg ``` Configure the unified persistence bundle without placing the connection string in source: ```ts title=".fabricharness/config.ts" import { postgresPersistence, type FabricHarnessConfig } from '@fabric-harness/node'; import { Pool } from 'pg'; export default function config(): FabricHarnessConfig { const pool = new Pool({ connectionString: process.env.DATABASE_URL }); return { persistence: postgresPersistence({ client: pool, tablePrefix: 'fabric_harness', }), }; } ``` `postgresPersistence()` migrates and wires session history, submissions, conversation offsets, attachments, finite run/event records, and cost-budget totals. `fh dev` and generated Node servers consume `persistence.serverOptions()` automatically, health checks can call `persistence.health()`, and server shutdown closes the injected pool. Use a distinct `tablePrefix` per environment or tenant boundary where operational separation is required. The lower-level `PostgresSessionStore`, `PostgresSubmissionStore`, `PostgresConversationStreamStore`, and `PostgresAttachmentStore` remain public for hosts that need to compose stores independently. For a durable single-node deployment, use the same surface with SQLite: ```ts title=".fabricharness/config.ts" import { sqlitePersistence } from '@fabric-harness/node'; export default { persistence: sqlitePersistence({ path: '.fabricharness/fabric-harness.sqlite', }), }; ``` The SQLite bundle includes sessions, submissions, conversation offsets, attachments, workflow run events, and atomic cost-budget totals. Use Postgres or Lakebase when multiple server replicas need to coordinate. For governed model-facing data access, scaffold a fixed-statement tool: ```sh fh add database postgres ``` ```ts import { postgresTool } from '@fabric-harness/databases/postgres'; const lookup = postgresTool({ client: pool, name: 'lookup_account', description: 'Read one account in the current tenant.', statement: 'select id, status from accounts where tenant_id = $1 and id = $2 limit 1', parameters: ({ tenantId, accountId }) => [tenantId, accountId], }); ``` The model supplies parameter values, never SQL text. Read tools reject mutation statements and multiple statements; all tools cap rows, result bytes, and execution time and declare an effect for policy/approval enforcement. See `examples/with-postgres-store` and run `pnpm run run:tool` for a runnable example. The same optional `DATABASE_URL` configuration is used by `examples/react-chat` and `examples/with-channel-adapters`, so the UI and channel examples can be run infrastructure-free or against durable Postgres without changing application code. The CI suite runs the published bundle contract against Postgres 16, including concurrent cost increments, lifecycle CAS behavior, cascade deletion, and close/reopen recovery. --- # Redis Canonical: https://harness.fabric.pro/docs/ecosystem/databases/redis Use Redis for complete runtime persistence or governed agent key-value tools. Redis has two separate integration surfaces: - `redisPersistence()` stores sessions, durable submissions, conversation offsets, attachments, runs, and cost budgets for Node runtimes. - `redisTools()` gives an agent bounded key-value capabilities. It does not expose persistence internals to the model. ## Runtime persistence Install the official Redis client alongside the Node runtime: ```sh pnpm add @fabric-harness/node redis ``` ```ts title=".fabricharness/config.ts" import { redisPersistence, type FabricHarnessConfig } from '@fabric-harness/node'; import { createClient } from 'redis'; export default async function config(): Promise { const client = createClient({ url: process.env.REDIS_URL }); await client.connect(); return { persistence: redisPersistence({ client, namespace: 'production', ttlSeconds: 30 * 24 * 60 * 60, }), }; } ``` All keys share a namespace-specific Redis Cluster hash tag. Mutations use an ownership-checked Lua commit, concurrent cost reservations are atomic, and binary attachments are preserved byte for byte. `ttlSeconds` applies sliding retention to persisted state; omit it when retention is managed externally. Use TLS, ACL credentials limited to the namespace, replication, and a persistence mode appropriate for your recovery objective. The same bundle works with Valkey deployments that support the Redis command and Lua surface used by the injected client. CI runs the complete persistence contract against Redis 7.4, including claims, settlement, fencing, restart recovery, deletion, one-megabyte attachments, and concurrency. ## Governed agent tools Install the namespaced Redis tool recipe: ```sh fh add database redis ``` ```ts const [get, set] = redisTools({ client, prefix: 'prod:acme', maxValueBytes: 64 * 1024, defaultTtlSeconds: 3600, }); ``` Every key is prefixed by application code. Values are size-bounded, writes can receive a TTL, and `redis_set` is marked `effect: 'write'`. The package intentionally exposes no script, admin, flush, or arbitrary-command tool. Use TLS and least-privilege ACL credentials. --- # SQLite Canonical: https://harness.fabric.pro/docs/ecosystem/databases/sqlite Use fixed, parameterized SQLite operations as governed agent tools. Scaffold `better-sqlite3` with the first-party adapter: ```sh fh add database sqlite ``` ```ts import { sqliteTool } from '@fabric-harness/databases/sqlite'; const lookup = sqliteTool({ client: database, name: 'lookup_account', description: 'Read one tenant-scoped account.', statement: 'select id, status from accounts where tenant_id = ? and id = ? limit 1', parameters: ({ tenantId, accountId }) => [tenantId, accountId], }); ``` The statement is fixed in application code. Read tools reject mutations and multiple statements; row, byte, and time limits protect model context. Mark controlled writes with `effect: 'write'` and attach approval policy as required. This is model-facing data access. For durable Harness sessions, configure the Node `store.backend: 'sqlite'` session store separately. --- # Supabase Canonical: https://harness.fabric.pro/docs/ecosystem/databases/supabase Build RLS-aware Supabase table and RPC tools. Run `fh add supabase` to install a compatible client and create a managed, fixed-table lookup tool and test. Preserve Row Level Security through end-user identity when required, and never expose the service-role key to model context. Generate table/RPC-specific tools with schema validation, limits, redaction, and approval for mutations. For Fabric runtime persistence, connect a server-side `pg.Pool` to Supabase's direct Postgres or session-pooler URL and pass it to `postgresPersistence()`. Do not use the browser Supabase client or service-role key in agent tools. Transaction-pooler compatibility depends on the connection mode; the direct connection is the reference configuration for migrations and long-lived Node hosts. --- # Turso Canonical: https://harness.fabric.pro/docs/ecosystem/databases/turso Connect Turso/libSQL with bounded, parameterized operations. Use the same full bundle as libSQL with remote credentials: ```ts const client = createClient({ url: process.env.TURSO_DATABASE_URL!, authToken: process.env.TURSO_AUTH_TOKEN!, }); export default { persistence: libsqlPersistence({ client }) }; ``` Keep URL/token secrets host-side. Mutations use optimistic version fencing and do not require a long-lived interactive transaction. Prefer the primary write URL for durable decisions; replicas can lag. Cascade deletion touches several snapshot rows, so retry it after a partial network failure. For model-facing data, run `fh add turso`. The managed recipe installs compatible libSQL packages, creates a fixed-statement tool and test, and keeps the URL/token in environment variables. Parameterize statements, enforce read/write separation, limits, tenant predicates, and mutation approval. --- # Valkey Canonical: https://harness.fabric.pro/docs/ecosystem/databases/valkey Use Valkey with ACLs, namespaces, and command policy. Run `fh add valkey` to install `iovalkey` and create a managed tenant-prefixed read tool and test. Use TLS/ACLs, command allowlists, value/TTL limits, and administrative/script/flush denial. Treat writes as governed effects. For runtime persistence, use `redisPersistence()` with a Redis-protocol client connected to Valkey; see the [Redis guide](/docs/ecosystem/databases/redis) for the full bundle, retention, cluster key, and recovery configuration. --- # Sandboxes Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes Remote execution adapters that implement the common SandboxEnv contract. Fabric owns the sandbox contract while applications own provider clients and credentials. The adapters below accept structural provider handles, so vendor SDKs remain optional peer dependencies. ```sh pnpm add @fabric-harness/connectors ``` | Provider | Helper | Recipe | Live validation | | --- | --- | --- | --- | | [Databricks SQL](/docs/ecosystem/sandboxes/databricks-sql) | `databricksSqlSandbox()` | First-party package | Workspace, Warehouse, and Unity Catalog gate. | | [boxd](/docs/ecosystem/sandboxes/boxd) | Generic `remoteSandbox()` | `fh add boxd` | Project-owned adapter and live smoke. | | [Cloudflare Shell](/docs/ecosystem/sandboxes/cloudflare-shell) | `@fabric-harness/cloudflare/shell` | `fh add cloudflare-shell` | Wrangler/account gate. | | [Cloudflare Sandbox](/docs/ecosystem/sandboxes/cloudflare) | `@fabric-harness/cloudflare` | `fh add cloudflare-sandbox` | Wrangler/account gate. | | [Daytona](/docs/ecosystem/sandboxes/daytona) | `daytonaSandbox()` | `fh add daytona` | `FABRIC_DAYTONA_TEST=1` | | [E2B](/docs/ecosystem/sandboxes/e2b) | `e2bSandbox()` | `fh add e2b` | `FABRIC_E2B_TEST=1` | | [exe.dev](/docs/ecosystem/sandboxes/exedev) | Generic `remoteSandbox()` | `fh add exedev` | Project-owned adapter and live smoke. | | [islo](/docs/ecosystem/sandboxes/islo) | Generic `remoteSandbox()` | `fh add islo` | Project-owned adapter and live smoke. | | [Mirage](/docs/ecosystem/sandboxes/mirage) | Generic `remoteSandbox()` | `fh add mirage` | Project-owned adapter and live smoke. | | [Modal](/docs/ecosystem/sandboxes/modal) | `modalSdkSandbox()` | `fh add modal` | `FABRIC_MODAL_TEST=1` plus Modal tokens | | [Vercel](/docs/ecosystem/sandboxes/vercel) | `vercelSandbox()` | `fh add vercel` | Contract tests; run a provider smoke before production. | | Kubernetes / AKS | `kubernetesSandbox()` | Package helper | Cluster-gated validation. | | Any provider | `remoteSandbox()` / `remoteSandboxEnv()` | `fh add --category sandbox` | Provider-specific. | All sandbox operations still pass through Fabric capability policy. Provider isolation does not replace filesystem, command, network, approval, timeout, or cleanup policy. --- # boxd Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/boxd Adapt boxd execution to Fabric's complete remote sandbox contract. Run `fh add boxd`. The versioned recipe installs the connector packages and creates a managed `RemoteSandboxApi` adapter plus its contract test. Implement the provider calls for shell exit semantics, binary files, cwd/environment, timeout/abort, lifecycle cleanup, and reconnectable references. Keep boxd credentials host-side, run `validateSandboxAdapter()`, and complete a real create/execute/cleanup smoke before production. --- # Cloudflare Sandbox Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/cloudflare Run Fabric jobs in Cloudflare container sandboxes. Run `fh add cloudflare-sandbox` to install compatible packages and create the managed binding adapter and test. Configure the binding through the Cloudflare build target and select sandbox mode in Fabric config. Each persistent agent instance is backed by Durable Object SQLite for submissions, conversation offsets, attachments, abort, and restart recovery; the sandbox binding provides the isolated Linux execution environment used by its tools. Enforce container CPU, memory, network, command, and cleanup limits in addition to Fabric capability policy. Run Wrangler and deployed-account execution tests before production. See [Cloudflare deployment](/docs/deployment/cloudflare). --- # Cloudflare Shell Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/cloudflare-shell Use Cloudflare Shell Workspace for edge filesystem and code operations. Run `fh add cloudflare-shell` to install compatible packages and create the managed Shell Workspace adapter and test, then bind a Worker Loader in Wrangler. Shell Workspace is not a Linux container and does not provide arbitrary bash or native package toolchains. Use Cloudflare Sandbox when those are required. Validate bindings in Wrangler and the target account, and use R2 for durable large content. --- # Daytona Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/daytona Run Fabric sessions in a Daytona-managed development sandbox. ```sh pnpm add @fabric-harness/connectors @daytona/sdk fh add daytona ``` ```ts import { Daytona } from '@daytona/sdk'; import { daytonaSandbox } from '@fabric-harness/connectors'; const client = new Daytona({ apiKey: process.env.DAYTONA_API_KEY }); const remote = await client.create({ image: 'node:22' }); const job = await init({ sandbox: daytonaSandbox(remote, { cleanup: true }), }); ``` The adapter maps Daytona files, commands, suspend/resume, and optional cleanup to `SandboxEnv`. Credentials remain on the Daytona client and are not serialized into session history. Use an `encodeRef` only when another process has a registered decoder capable of reconnecting to the remote sandbox. See `examples/with-daytona` for the runnable adapter shape. --- # E2B Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/e2b Adapt an E2B sandbox to Fabric shell and filesystem operations. ```sh pnpm add @fabric-harness/connectors @e2b/code-interpreter fh add e2b ``` ```ts import { Sandbox } from '@e2b/code-interpreter'; import { e2bSandbox } from '@fabric-harness/connectors'; const remote = await Sandbox.create({ apiKey: process.env.E2B_API_KEY }); const fabric = await init({ sandbox: e2bSandbox(remote, { cleanup: true }), }); ``` The structural adapter maps E2B command and file APIs, including pause/resume when present. `{ cleanup: true }` invokes the provider's conventional termination method when Fabric disposes the sandbox. Run the connector live suite with `FABRIC_E2B_TEST=1` and `E2B_API_KEY` before promoting an SDK version or template change. --- # exe.dev Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/exedev Adapt exe.dev machines to Fabric shell and filesystem operations. Run `fh add exedev`. The versioned recipe creates a managed `remoteSandboxEnv()` adapter and test; implement the current machine API mapping for cwd, environment allowlists, abort, timeout, binary files, cleanup, and reconnection behavior. Never serialize SSH keys or machine tokens into model context, history, or portable refs. Validate the adapter structurally and live. --- # islo Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/islo Build an islo adapter against Fabric's portable sandbox contract. Run `fh add islo`. The versioned recipe creates a managed remote adapter and test; map the provider's current SDK and authentication to Fabric's sandbox contract, policy enforcement, and lifecycle expectations. Define resource/network limits, deterministic cleanup, and portable refs only when another process can securely reconnect. --- # Mirage Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/mirage Adapt Mirage isolated execution to Fabric agents. Run `fh add mirage`. The versioned recipe creates a managed remote adapter and test; implement the complete shell/filesystem/lifecycle interface, preserve provider exit/timeout/abort semantics, and keep authentication outside model/session data. Provider isolation supplements rather than replaces Fabric command, filesystem, network, approval, and audit controls. --- # Modal Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/modal Run Fabric sessions in Modal sandboxes through the first-party TypeScript SDK adapter. ```sh pnpm add @fabric-harness/connectors modal fh add modal ``` Create a Modal sandbox with the provider SDK and pass the native handle to Fabric: ```ts import { ModalClient } from 'modal'; import { modalSdkSandbox } from '@fabric-harness/connectors/modal'; const client = new ModalClient(); const app = await client.apps.fromName('fabric-harness', { createIfMissing: true }); const image = client.images.fromRegistry('node:22-alpine'); const remote = await client.sandboxes.create(app, image, { timeoutMs: 300_000, workdir: '/workspace', }); const sandbox = modalSdkSandbox(remote, { cleanup: true }); const fabric = await init({ sandbox }); ``` The adapter maps Modal's streamed process handles, binary filesystem API, timeout, portable `sandboxId`, reconnect, and termination behavior to `SandboxEnv`. Credentials stay inside `ModalClient`; they are never serialized into a session or model request. The runnable `examples/with-modal` project uses this path, and the protected live suite publishes the nine-check sandbox certification report for Modal SDK 0.9. --- # Vercel Sandbox Canonical: https://harness.fabric.pro/docs/ecosystem/sandboxes/vercel Adapt @vercel/sandbox instances to Fabric sessions. ```sh pnpm add @fabric-harness/connectors @vercel/sandbox fh add vercel ``` ```ts import { Sandbox } from '@vercel/sandbox'; import { vercelSandbox } from '@fabric-harness/connectors/vercel'; const remote = await Sandbox.create({ runtime: 'node24', timeout: 300_000, }); const fabric = await init({ sandbox: vercelSandbox(remote, { cleanup: true }), }); ``` The connector maps the Vercel 1.x command object, async output readers, output streams, `AbortSignal`, file APIs, and lifecycle operations without exposing provider credentials to the model. The supported range is `@vercel/sandbox >=1.10.1 <2`. `sandboxId` becomes a portable ref automatically. Register the receiving process once: ```ts registerStandardSandboxRefDecoders({ vercel: { connect: ({ sandboxId }) => Sandbox.get({ sandboxId }) }, }); ``` Run `assertSandboxCertification()` from the [sandbox connector guide](/docs/building/sandbox-connectors#certification-helper) in the deployment account. The live workflow writes the secret-free report as a CI artifact. --- # Tooling Canonical: https://harness.fabric.pro/docs/ecosystem/tooling Evaluation and observability integrations that plug into Fabric evals, events, and telemetry. Fabric owns the eval and telemetry contracts (`@fabric-harness/evals`, `SubmissionTelemetrySink`, OTel span exporters) while applications own vendor accounts and credentials. The integrations below build on those seams, so vendor SDKs remain optional dependencies of your workspace. | Integration | What it adds | Guide | | --- | --- | --- | | [Braintrust](/docs/ecosystem/tooling/braintrust) | Eval logging and experiment comparison for `fh test` suites. | Recipe + env wiring. | | [Jetty](/docs/ecosystem/tooling/jetty) | Grades job output with a separately deployed rubric and stores comparable trajectories. | Deployment + rubric setup. | | [OpenTelemetry](/docs/ecosystem/tooling/opentelemetry) | Span export for prompts, tools, and submissions to any OTLP collector. | Exporter configuration. | | [Sentry](/docs/ecosystem/tooling/sentry) | Error capture for server routes and agent turns. | DSN + event hook wiring. | | [Vitest evals](/docs/ecosystem/tooling/vitest-evals) | Runs eval suites inside your existing Vitest setup. | Suite conventions. | Telemetry integrations observe execution — they never gate it. A sink or exporter failure is reported and swallowed; agent turns and submissions settle regardless. --- # Braintrust Canonical: https://harness.fabric.pro/docs/ecosystem/tooling/braintrust Export Fabric traces and evaluation context to Braintrust. Run `fh add braintrust` to install the current SDK and generate a managed, redacted observer plus its contract test. Attach stable job/agent/session/model metadata and keep evaluation datasets separate from production session storage. ```ts import { createObservabilityObserver } from '@fabric-harness/sdk'; import { initLogger } from 'braintrust'; const logger = initLogger({ projectName: 'fabric-harness' }); const onEvent = createObservabilityObserver({ integration: 'braintrust', correlation: { agentId: 'support', jobId: runId, submissionId, tenantId }, send: (record) => logger.log({ input: record.event, metadata: record.correlation }), }); const agent = await init({ onEvent }); ``` Prompt, output, tool, and customer content capture must be explicitly enabled and redacted. Keep the Braintrust API key host-side and flush the client during process shutdown. The default record contains event identity and correlation only. Set `captureData: true` only after reviewing the recursive key/secret redaction policy. A runnable fixture is in `examples/with-observability`. --- # Jetty Canonical: https://harness.fabric.pro/docs/ecosystem/tooling/jetty Grade Fabric Harness job output and compare results across versions with Jetty. Jetty can grade output from a Fabric Harness job and store the grading task as a trajectory. Use it when you need a persistent, comparable record of quality across prompts, models, or releases. Use [Fabric evals](/docs/building/evals) for fast local and CI assertions. ## Install ```sh fh add jetty ``` The managed recipe pins the published `@jetty/sdk 0.2.x` contract and creates a typed `gradeOutputWithJetty()` helper and verification test. Create and deploy a Jetty grading runbook before invoking it from Fabric Harness. The runbook must produce the `grade.json` expected by `gradeWithJetty()`. ## Grade a job response ```ts title=".fabricharness/jobs/evaluate-triage.ts" import { defineJob, schema } from '@fabric-harness/sdk'; import { gradeWithJetty, JettyClient } from '@jetty/sdk'; interface TriageGrade { total: number; pass: boolean; } const jetty = new JettyClient(); export default defineJob({ name: 'evaluate-triage', input: schema.object({ ticket: schema.string() }), triggers: { manual: true }, async run({ input, prompt, run }) { const response = await prompt(`Triage this support ticket:\n\n${input.ticket}`); const { grade, trajectoryId } = await gradeWithJetty( jetty, process.env.JETTY_COLLECTION!, process.env.JETTY_GRADE_TASK!, { files: [{ filename: 'case.json', data: JSON.stringify({ ticket: input.ticket, response }), }], useTrialKeys: process.env.JETTY_USE_TRIAL_KEYS === 'true', labels: (result) => ({ 'eval.grade': String(result.total), 'eval.pass': String(result.pass), 'fabric.run_id': run?.runId ?? 'local', }), }, ); return { response, grade, trajectoryId }; }, }); ``` Run it on the Node target: ```sh fh run evaluate-triage --ticket "Customers cannot reset passwords" ``` `@jetty/sdk` requires Node.js. Keep the grader separate from the job being evaluated so a prompt change cannot silently change its own rubric. ## Environment | Variable | Purpose | | --- | --- | | `JETTY_API_TOKEN` | Authenticates the Jetty SDK. The SDK can also use its local token configuration. | | `JETTY_COLLECTION` | Collection that owns the grading task. | | `JETTY_GRADE_TASK` | Deployed grading task identifier. | | `JETTY_USE_TRIAL_KEYS` | Optional; set to `true` to use Jetty trial model keys. | The Fabric job still needs its model-provider credentials. Jetty credentials configure only the grading operation. ## Sensitive data Jetty trajectories may persist uploaded files, inputs, and outputs. Redact credentials, personal data, and regulated content before grading. Put grader credentials in Jetty secret parameters rather than initialization parameters or uploaded files, and review retention and access controls before grading production content. For a credential-free automated fixture using the same correlation and redaction contract, see `examples/with-observability`. --- # OpenTelemetry Canonical: https://harness.fabric.pro/docs/ecosystem/tooling/opentelemetry Export Fabric agent, model, tool, task, and shell activity as spans. Fabric provides flat event exporters and a hierarchical observer. Your application owns the OpenTelemetry SDK, exporter, sampling, credentials, and shutdown lifecycle. ```sh fh add opentelemetry ``` The managed recipe installs the OpenTelemetry API and creates a hierarchical observer configured with content redaction and Foundry-compatible semantic conventions. ```ts import { trace } from '@opentelemetry/api'; import { init } from '@fabric-harness/sdk'; import { createOpenTelemetryObserver } from '@fabric-harness/sdk/otel-observer'; const observe = createOpenTelemetryObserver({ tracer: trace.getTracer('support-agent'), conventions: 'foundry', correlation: { agentId: 'support', jobId: runId, submissionId, tenantId }, }); const fabric = await init({ onEvent: observe }); ``` The observer creates nested turn, tool, shell, and task spans. `conventions: 'foundry'` emits `gen_ai.*` names and attributes compatible with Azure AI Foundry and OpenTelemetry GenAI consumers. Stable job, agent, session, submission, and tenant IDs are span attributes. Use `openTelemetryExporter()` when one flat span per duration-bearing Fabric event is preferable. Do not export prompts, tool arguments, or results without a deliberate redaction and retention policy. See [Telemetry](/docs/reference/telemetry) for exporters, attributes, and event coverage. --- # Sentry Canonical: https://harness.fabric.pro/docs/ecosystem/tooling/sentry Connect Fabric runtime tracing and errors to Sentry. Run `fh add sentry` to install the current Node SDK and generate a managed redacted event observer and test. Initialize Sentry before the Fabric runtime and attach stable job, agent, session, submission, and tenant-safe correlation fields. ```ts import * as Sentry from '@sentry/node'; import { createObservabilityObserver } from '@fabric-harness/sdk'; const onEvent = createObservabilityObserver({ integration: 'sentry', correlation: { agentId: 'support', jobId: runId, submissionId, tenantId }, send(record) { Sentry.addBreadcrumb({ category: 'fabric', message: record.event.type, data: record.correlation }); }, }); ``` Exclude secrets, prompts, tool arguments/results, and customer data unless an explicit scrubbed capture policy permits them. Flush Sentry during graceful shutdown. --- # Vitest Evals Canonical: https://harness.fabric.pro/docs/ecosystem/tooling/vitest-evals Combine Fabric eval suites with focused Vitest regression tests. Fabric ships `@fabric-harness/evals` for case/scorer execution and uses Vitest for repository-level regression tests. Keep behavioral eval suites in `*.eval.ts`; use ordinary Vitest tests for deterministic runtime contracts. ```sh fh add vitest-evals ``` The managed recipe creates a credential-free `defineEvalSuite()` fixture and Vitest contract. Add `@fabric-harness/node` when the suite invokes a project agent rather than a pure runner function. ```ts title="test/hello.test.ts" import { describe, expect, it } from 'vitest'; import { runAgent } from '@fabric-harness/node'; describe('hello job', () => { it('runs without provider credentials', async () => { const run = await runAgent({ agent: 'hello', payload: { name: 'Ada' }, mock: true, }); expect(run.result).toContain('Ada'); }); }); ``` For multi-case scoring, use `defineEvalSuite()` and run `fh test`. CI can run both gates: ```sh pnpm vitest run fh test --json > eval-results.json ``` Eval artifacts can include prompts, outputs, tool results, and model judgments. Review them before uploading to CI or a hosted evaluation service. See [Evaluations](/docs/building/evals) and the [eval API](/docs/reference/eval-library). --- # Examples Canonical: https://harness.fabric.pro/docs/examples Runnable agents demonstrating fabric-harness features and deploy targets. Every example below is a self-contained workspace under `examples/` in the [fabric-harness repo](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples). Clone, install, and run. ## Quickstart ```sh git clone https://github.com/Fabric-Pro/fabric-harness cd fabric-harness/examples/ pnpm install pnpm exec fabric-harness agents pnpm exec fabric-harness run ``` ## By feature | Example | What it shows | Deploy target | | --- | --- | --- | | [`hello-world`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/hello-world) | Bare minimum: one agent, one prompt. | Node | | [`minimal`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/minimal) | Smallest headless finite job. | Node | | [`with-config`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-config) | Workspace-level `config.ts` overrides. | Node | | [`with-approval`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-approval) | Imperative approval gate via `session.approval.request()`. | Node | | [`with-checkpoint`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-checkpoint) | Snapshot a session, restart from a labeled checkpoint. | Node | | [`with-skill`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-skill) | `session.skill()` invocation with typed result. | Node | | [`with-task`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-task) | Long-running tasks with checkpoints. | Node / Temporal | | [`with-tools`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-tools) | Custom tool schemas. | Node | | [`with-local-shell`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-local-shell) | `LocalSandboxEnv` shell tool. | Node | ## Deploy targets | Example | Target | | --- | --- | | [`with-docker`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-docker) | Docker sandbox | | [`with-kubernetes`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-kubernetes) | Kubernetes pod | | [`with-cloudflare-sandbox`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-cloudflare-sandbox) | Cloudflare Containers sandbox | | [`with-cloudflare-workers-ai`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-cloudflare-workers-ai) | Cloudflare Workers AI binding (zero-API-key) | | [`with-azure`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-azure) | Azure ACA / AKS | | [`with-daytona`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-daytona) | Daytona remote sandbox | | [`with-modal`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-modal) | Modal sandbox | | [`with-s3-source`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-s3-source) | Mount an S3 prefix as a filesystem source | | [`with-temporal`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-temporal) | Durable Temporal worker | | [`with-databricks-simple`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-simple) | Credential-free Databricks authoring smoke with a mocked model provider | | [`with-databricks`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks) | Governed SQL/Unity Catalog analytics copilot; live Databricks required | | [`with-databricks-rag`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-rag) | Vector Search plus Unity AI Gateway RAG; live Databricks required | | [`with-databricks-dataeng`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-dataeng) | Lakeflow pipeline operations; live Databricks required | | [`with-databricks-cost-attribution`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-databricks-cost-attribution) | System Tables cost attribution with a mocked REST client | | [`with-postgres-store`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-postgres-store) | Postgres session persistence | | [`with-slack-channel`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-slack-channel) | Verified Slack event ingress to a persistent agent | | [`with-channel-adapters`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-channel-adapters) | All 17 maintained channel adapters with signed ingress and governed replies | | [`with-signal-messages`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-signal-messages) | Out-of-band typed signal messages | | [`with-packaged-skills`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/with-packaged-skills) | Packaged skills and lazy resource loading | | [`finite-jobs`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/finite-jobs) | Typed parent/child jobs, middleware, run identity, and idempotent nested invocation | | [`scheduled-jobs`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/scheduled-jobs) | Node cron scheduling, Cloudflare Cron build output, and HA lease guidance | | [`database-persistence`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/database-persistence) | One config selecting local libSQL, Turso, MySQL, or MongoDB unified persistence | | [`application-routes`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/application-routes) | Authenticated Fetch routes and middleware composing durable dispatch and finite jobs | ## Realistic use cases | Example | What it does | | --- | --- | | [`code-review`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/code-review) | Review a PR diff, produce inline comments. | | [`issue-triage-ci`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/issue-triage-ci) | GH Actions agent that triages new issues. | | [`bug-reproducer`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/bug-reproducer) | Generate a minimal reproduction from a bug report. | | [`data-analyst`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/data-analyst) | Mount a CSV; answer SELECT-style questions. | | [`support-agent`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/support-agent) | KB-grounded customer support. | | [`support-agent-cloudflare-r2`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/support-agent-cloudflare-r2) | Same, deployed on Cloudflare with R2-backed KB. | | [`support-agent-foundry`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/support-agent-foundry) | Same, on Azure AI Foundry Hosted Agent. | | [`changelog-writer`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/changelog-writer) | Drafts release notes from git log. | | [`incident-runbook`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/incident-runbook) | Runs a runbook against an alert payload. | | [`api-docs-generator`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/api-docs-generator) | Generate API docs from source. | | [`schema-migration`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/schema-migration) | Plan a database schema migration. | | [`test-generator`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/test-generator) | Generate Vitest tests from source. | | [`dependency-auditor`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/dependency-auditor) | Audit npm dependencies for risk. | | [`coding-agent-lite`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/coding-agent-lite) | Minimal Claude-Code-style coding agent. | | [`remote-coding-agent`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/remote-coding-agent) | Coding agent in a remote sandbox. | | [`release-notes`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/release-notes) | Compose release notes from PRs and commits. | | [`voice-data-collector`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/voice-data-collector) | Voice intake — Realtime API + bring-your-own audio I/O. | ## Need a connector? When no example covers the provider you want, scaffold one in your own project: ```sh fh add daytona | claude fh add https://your-provider.example.com --category sandbox | claude fh add daytona --pr # open a draft PR with a stub ``` See the [connector catalog](/docs/building/connector-catalog) for the spec. ## What "runnable" means Credential-free examples demonstrate local wiring and deterministic model behavior. Provider-backed examples require the named provider, credentials, permissions, and the smoke test documented by that integration. Follow the [Databricks deployment guide](/docs/deployment/databricks) for workspace identity and certification. ## Follow-along guides - [Enterprise controls](/docs/building/enterprise-controls) — combine policy, approvals, tools, and budgets. - [Build and run artifacts](/docs/deployment/build-artifacts) — package finite jobs and persistent agents. - [Databricks App tutorial](/docs/deployment/databricks-app) — scaffold, mock-test, build, deploy, and add Lakebase. The credential-free Databricks examples cover the authoring flow. Configure the credentialed suite to verify Lakebase OAuth database credential exchange, App deployment, workspace permissions, and restart recovery in the target workspace. --- # Configuration Canonical: https://harness.fabric.pro/docs/getting-started/configuration config.ts, environment variables, and precedence. Most agents work with zero configuration. When you need defaults across agents — a model, a Temporal address, a session-store backend, a capability policy, a sandbox profile — put them in `.fabricharness/config.ts`. ## `.fabricharness/config.ts` ```ts import type { FabricHarnessConfig } from '@fabric-harness/node'; const config: FabricHarnessConfig = { agent: { model: 'openai/gpt-5.5', // Optional capability policy applied to every agent unless overridden by init(): policy: { commandPolicy: { allow: ['git status*', 'git log*', 'gh issue view*'], requireApproval: ['gh issue comment*', 'psql*'], deny: ['git push*', 'rm -rf*'], }, maxCommandTimeoutMs: 30_000, approvals: { requiredApprovals: 1, defaultTimeoutMs: 60_000, risk: 'medium' }, }, }, run: { target: 'node', // 'node' | 'temporal-worker' | 'cloudflare' model: 'openai/gpt-5.5', idPrefix: 'fab', cwd: 'project', // default sandbox/session cwd for fh run }, temporal: { address: 'localhost:7233', taskQueue: 'fabric-harness', namespace: 'default', workflowIdPrefix: 'fabric-cli', promptWorkflowMode: 'hybrid', }, sandbox: { backend: 'local', // 'virtual' | 'empty' | 'local' | 'docker' | 'cloudflare' metadata: { inheritSafeEnv: true, envAllowlist: ['GH_TOKEN', 'DATABASE_URL'], defaultTimeoutMs: 30_000, outputLimitBytes: 256_000, }, }, store: { backend: 'file', // 'file' | 'sqlite' | 'postgres' | 'cloudflare' }, cloudflare: { workerName: 'my-agent', routes: [{ pattern: 'api.example.com/agents/*', zoneName: 'example.com' }], bindings: { kv: [{ binding: 'SESSIONS' }], r2: [{ binding: 'KB' }], d1: [{ binding: 'DB' }], }, }, }; export default config; ``` The config is loaded dynamically — you can use TypeScript freely (imports, type checks, conditional logic, etc.). ## Precedence For each setting, the highest source wins: ``` CLI flag > environment variable > config.ts > agent default ``` Common environment variables: | Variable | Purpose | | --- | --- | | `FABRIC_MODEL` | Default `provider/model-id` for `fh run`. | | `FABRIC_TARGET` / `FABRIC_HARNESS_TARGET` | Default `--target` for `fh run`. | | `FABRIC_TEMPORAL_TASK_QUEUE` | Default Temporal task queue. | | `FABRIC_TEMPORAL_ADDRESS` | Default Temporal address. | | `FABRIC_TEMPORAL_NAMESPACE` | Default Temporal namespace. | | `FABRIC_DOCKER_TEST` | Gates live Docker tests (`=1` to enable). | | `OPENAI_API_KEY`, `AZURE_OPENAI_*`, etc. | Provider credentials. | ## Loading `.env` files Fabric Harness auto-loads common env files from the repo root and workspace root: ```txt .env .env.local .fabricharness/.env .fabricharness/.env.local ``` For local development, put provider keys once in the repo-level `.env.local`: ```sh cp .env.example .env.local # edit .env.local and set OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. ``` Shell environment values always win. Use explicit `--env ` only for overrides; repeatable explicit env files override auto-loaded files but not shell env. ```sh fh run ask --env .env.test --question "hi" fh dev --env .env.test fh temporal-worker --env .env.test ``` `--env` is supported on `run`, `dev`, and `temporal-worker`. ## Agent-level overrides A metadata agent can declare its own defaults: ```ts export default agent({ name: 'ask', model: 'openai/gpt-5.5', // wins over config.run.model target: 'node', // wins over config.run.target // ... }); ``` CLI flags still override agent defaults. ## Where config files live in the build `fh build` reads `.fabricharness/config.ts` at build time and bakes the relevant subset into the manifest. Runtime-sensitive values (Temporal address, secrets, etc.) stay env-driven so they can change per environment. --- # Your First Agent Canonical: https://harness.fabric.pro/docs/getting-started/first-agent Build, describe, and run an agent end-to-end — bare and /strict imports. import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; This walkthrough builds an `ask` agent two ways: the **default** import for headless defaults, and the **`/strict`** import when you want every option declared in source (Temporal, compliance/audit, replay-deterministic workloads). Both run through the same CLI and share the same session primitives. > The runtime (`stateless`, `inline`, `temporal`) is configured at `init()` and is independent of which entrypoint you import from. ## Fastest path: `fh init` ```sh npx @fabric-harness/cli init my-first-agent cd my-first-agent npm install fh dev --mock ``` `fh init` scaffolds a runnable agent, role, skill, and config. Skip ahead to step 4 below to test it. Choose persistence explicitly when the project needs it: ```sh fh init support-app --template minimal --store memory fh init support-app --template minimal --store sqlite fh init support-app --template minimal --store postgres ``` `memory` is the infrastructure-free unified bundle. `postgres` generates `postgresPersistence()` and reads `DATABASE_URL` from the environment. `file` and `sqlite` configure the existing local session backends; use Postgres or Lakebase when submissions, streams, attachments, and finite runs must survive process replacement. Templates include `default`, `minimal`, `data-analyst`, `support-agent`, `cloudflare`, `temporal`, and `databricks`. ## 1. Create the workspace (manual) A Fabric Harness workspace is any directory with a `.fabricharness/` folder. ```sh mkdir my-first-agent cd my-first-agent mkdir -p .fabricharness/jobs npm init -y npm install @fabric-harness/sdk @fabric-harness/cli ``` ## 2. Write the agent Create `.fabricharness/jobs/ask.ts`: ```ts import { defineJob, schema } from '@fabric-harness/sdk'; export default defineJob({ name: 'ask', input: schema.object({ question: schema.string() }), output: schema.string(), triggers: { webhook: true }, run: ({ input, prompt }) => prompt(input.question), }); ``` `defineJob({...})` lazily creates one default session and exposes `prompt`, `skill`, `task`, and `shell` directly in `run`. Use `session()` when you need a named session or another session-level API. Headless defaults still apply through the bare SDK import. ```ts import { agent, schema } from '@fabric-harness/sdk/strict'; export default agent({ name: 'ask', description: 'Answers a question using the configured model.', input: schema.object({ question: schema.string().describe('Question to answer'), }), output: schema.string(), model: process.env.FABRIC_MODEL ?? 'openai/gpt-5.5', triggers: { webhook: true }, run: async ({ init, input }) => { const fabricAgent = await init({ runtime: 'inline', sandbox: 'local', compaction: { enabled: false }, }); const session = await fabricAgent.session(); return await session.prompt(input.question); }, }); ``` From `/strict` every option is declared in source — nothing implicit. Required for Temporal-backed durability and recommended for compliance workloads. Both forms register a finite job (`fh describe ask` works on either). Either import supports schemas, policy, artifacts, and skills — strict just refuses to inject defaults. `triggers.webhook: true` exposes the job at `POST /jobs/ask` on Node-derived targets and Cloudflare. ## 3. List and describe From the workspace root: ```sh fh agents fh describe ask fh run ask --question "What is Fabric Harness?" --mock ``` `describe` prints the input/output schema, declared model, default target, and any examples. Use `--json` if you need machine-readable output. ## 4. Run it ```sh fh run ask --question "What is Temporal?" --mock ``` Behind the scenes the CLI: 1. Discovers `.fabricharness/jobs/ask.ts`. 2. Loads workspace config (`.fabricharness/config.ts`, optional). 3. Picks a model — CLI flag → `FABRIC_MODEL` env → config → agent default. 4. Validates the input against the declared Fabric schema. 5. Calls `run({ input, prompt, skill, task, shell, session })`. 6. Validates the output against the declared Fabric schema. 7. Persists the session under `.fabricharness/sessions/`. Other ways to pass payload: ```sh fh run ask --payload '{"question":"What is Temporal?"}' fh run ask question="What is Temporal?" fh run ask --payload-file input.json echo '{"question":"hi"}' | fh run ask --stdin ``` ## 5. Use a real model Put provider keys once in the repo-level `.env.local`; Fabric Harness auto-loads repo/workspace `.env` and `.env.local` files, and shell env still wins. ```sh cp .env.example .env.local # edit .env.local and set OPENAI_API_KEY=... fh run ask --model openai/gpt-5.5 --question "What is Temporal?" ``` For repeated use, put the model in `.fabricharness/config.ts` so you do not need `--model` either. > **Never** paste API keys into source files or session artifacts. Use `.env.local`, a secret store, or shell environment variables. ## 6. Inspect what happened ```sh fh sessions fh inspect fh logs fh metrics ``` ## Next steps - [Workspace layout](/docs/getting-started/workspace-layout) — the rest of `.fabricharness/`. - [Configuration](/docs/getting-started/configuration) — `config.ts`, env precedence, model defaults. - [Building agents](/docs/building/anatomy) — skills, roles, tools, sandboxes. - [CLI reference](/docs/cli) — every command. --- # Fabric Harness in 5 minutes Canonical: https://harness.fabric.pro/docs/getting-started/five-minutes Create, validate, and run a minimal Fabric Harness agent with no Docker or cloud setup. Fabric Harness starts with one TypeScript definition: call `init()`, open a session, and prompt it. The default path uses the lightweight virtual sandbox, so you do not need Docker, Temporal, Cloudflare, or a remote sandbox to start. ## Create an agent ```sh npx @fabric-harness/cli init my-agent --template minimal cd my-agent npm install ``` ## Validate readiness ```sh npx fabric-harness doctor --getting-started --tools ``` This checks Node, ESM package setup, Fabric dependencies, agent discovery, session storage, model resolution, and built-in tool schemas. ## Run it ```sh npx fabric-harness agents npx fabric-harness run hello --name Preetham --mock ``` Or start the HTTP/SSE dev server: ```sh npx fabric-harness dev --mock curl http://localhost:3000/jobs/hello \ -H 'content-type: application/json' \ -d '{"name":"Preetham"}' ``` The mock provider requires no API key and still exercises discovery, schema validation, the model loop, and HTTP routing. Remove `--mock` after configuring a real provider. Create more definitions without writing boilerplate: ```sh fh new job summarize-report fh new agent support ``` ## Choose the next runtime | Need | Use | | --- | --- | | Fast no-container agent | `sandbox: 'virtual'` | | CI/repo automation with host tools | `sandbox: 'local'` plus scoped `defineCommand()` commands | | Untrusted code or data analysis | Docker sandbox | | Human approval delays or restart durability | Temporal runtime | | Edge/serverless endpoint | Cloudflare target | Start with the minimal template, then add only the enterprise controls your use case needs: policy, approvals, audit, cost budgets, durable workflows, and deployment attestations. --- # Headless agents with the default import Canonical: https://harness.fabric.pro/docs/getting-started/headless-mode The 10-line path to a working headless agent — agent({...}) from @fabric-harness/sdk with headless defaults injected. Fabric Harness separates three choices that are easy to mix up: - **Entrypoint** — `@fabric-harness/sdk` for headless defaults, or `@fabric-harness/sdk/strict` for no implicit behaviour. - **Runtime** — `stateless`, `inline`, or `temporal`. - **Target** — where the agent runs, such as `node`, `temporal-worker`, `docker`, or `cloudflare`. This page shows the smallest headless setup: the bare `@fabric-harness/sdk` import + headless defaults + `@fabric-harness/sdk`. The same `init()`, `session.prompt()`, `session.shell()`, `session.skill()`, and `session.task()` APIs work from `@fabric-harness/sdk/strict` too. > **One SDK, two import entrypoints.** The minimal entrypoint is not a separate framework and not a less capable runtime. It keeps imports small and applies headless defaults. Durability still comes from `runtime`, not the import path. ## The 7-line agent ```ts title=".fabricharness/agents/echo.ts" import { agent } from '@fabric-harness/sdk'; export default agent<{ message: string }>({ name: 'echo', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init({ model: 'openai/gpt-5.5' })).session(); return { reply: await session.prompt(input.message) }; }, }); ``` Run it: ```bash export FABRIC_MODEL=openai/gpt-5.5 export OPENAI_API_KEY=... fabric-harness run echo --message "hello" ``` Or expose it as a webhook: ```bash fabric-harness dev curl -X POST http://localhost:8787/agents/echo \ -H "Content-Type: application/json" \ -d '{"message":"hello"}' ``` That's it. No schemas to define, no policy to author, no session store to configure. ### Defaults applied automatically the bare `@fabric-harness/sdk` import injects three headless defaults so the snippet stays minimal. Each is overridable per call: | Default | Why | Override with | | --- | --- | --- | | `runtime: 'stateless'` | Headless / edge / one-shot agents often do not need persistence | `init({ runtime: 'inline' })` to keep an in-memory session, or `'temporal'` for durable workflows | | `sandbox: 'virtual'` | In-memory bash + filesystem (`grep`, `glob`, `read`, `cat`, `mkdir`, `rm`, `echo`) via [`just-bash`](https://github.com/vercel-labs/just-bash). No host shell access. | `init({ sandbox: 'local' })` for host shell, `'docker'` for isolation, or a `SandboxFactory` | | `loopRuntime: pi-agent-core` | Multi-provider model coverage from [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai) — Anthropic, OpenAI, Google, OpenAI-compatible proxies, OpenRouter, etc. | `init({ loopRuntime: defaultLoopRuntime })` to use Fabric's `NativeLoopRuntime` (with capability-policy + result-retry hooks) | For agents with typed I/O, capability policy, approvals, artifacts, or explicit metadata, use `agent({...})` directly from `@fabric-harness/sdk/strict`. You can also keep `agent({ run: , })` and change imports first; the session primitives do not change. ## What the pieces do ### the bare `@fabric-harness/sdk` import `agent({ options?, run: handler, })` is a thin shorthand for `agent({ run: handler, ...options })` with no input/output schema. The payload is `unknown`, the return value passes through unchanged. Use it when typed validation is more ceremony than value. ### `runtime: 'stateless'` Disables session persistence. No store writes, no artifact disk persistence, no approval waiting. Each invocation is independent. This is the right choice for: - High-volume webhook handlers where state would just be discarded. - Edge runtimes (Cloudflare Workers, Vercel Edge) with no durable storage attached. - Quick prototypes where conversation continuity isn't yet a concern. In production (`FABRIC_ENV=production` or `NODE_ENV=production`), choosing `inline` without an explicit `SessionStore` will emit a warning unless you set `FABRIC_ALLOW_EPHEMERAL_STATE=1` or pick `runtime: 'stateless'` explicitly. This prevents surprise data loss. ### `@fabric-harness/sdk` A minimal entrypoint that re-exports the headless surface — `init`, the bare `@fabric-harness/sdk` import, `agent`, `schema`, `defineCommand`, sandbox helpers, MCP — without pulling Temporal, durable stores, or telemetry exporters into the import graph. Smaller bundles for edge deployments. For the complete export surface, import from `@fabric-harness/sdk` directly. Both entrypoints share the same runtime and session primitives. ## Minimal does not mean feature-light The minimal entrypoint skips import weight and ceremony, not the shared agent primitives. Skills, roles, scoped commands, MCP tools, and tasks all work — they just do not require typed input/output metadata or a policy authoring step. ### Skills (Markdown-defined procedures) Drop a Markdown file under `.fabricharness/skills/` and call it from your agent. Skills are discovered automatically by the workspace loader. ```md title=".fabricharness/skills/triager.md" --- name: triager description: Search the knowledge base for the best matching article and write a concise, friendly reply. --- You are a customer support triager. Steps: 1. Use grep / glob / read over `/workspace/kb` to find articles relevant to the customer's question. 2. Quote the most directly relevant lines. 3. Write a 2–4 sentence reply in the brand's voice (friendly, concise, no jargon). 4. If nothing matches, say so honestly and suggest the customer contact support@example.com. ``` ```ts title=".fabricharness/agents/support.ts" import { agent } from '@fabric-harness/sdk'; export default agent({ name: 'support', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init({ runtime: 'stateless' })).session(); const message = String((payload as any)?.message ?? ''); const reply = await session.skill('triager', { args: { message } }); return { reply }; }, }); ``` `session.skill('triager', { args })` injects the skill body as the prompt, templates the args in, and returns the model's response. The same primitive exists from both entrypoints. Complete metadata agents can also declare default inline skills with `agent({ skills: [...] })` and can add `result: schema` for typed validation. ### Roles (system-prompt overlays) Roles let you change the agent's voice or expertise without rewriting the prompt every time. Drop a Markdown file under `.fabricharness/roles/`: ```md title=".fabricharness/roles/concise.md" --- name: concise description: Reply in 1-3 sentences. No bullet points unless explicitly asked. Plain prose only. --- You write extremely concise replies. Aim for 1–3 sentences. Use plain prose, not bullet points or headings, unless the user asked for structured output. Never repeat the question back. Never apologize for the length of the answer. ``` Use it on a session, prompt, or skill call: ```ts const session = await agent.session('thread-1', { role: 'concise' }); await session.prompt('What does this code do?'); // uses 'concise' await session.skill('triager', { args, role: 'concise' }); // override on the call ``` Precedence: per-call `role` > session `role` > agent `role`. ### Scoped commands (privileged CLIs) `defineCommand` binds a CLI like `gh` or `npm` with its env (and therefore its secrets) at the *command* level — secrets never enter model context. Grant the command per-call: ```ts import { agent, defineCommand } from '@fabric-harness/sdk'; const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } }); const npm = defineCommand('npm'); export default agent({ name: 'triage', run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local', runtime: 'stateless' })).session(); const result = await session.skill('triage-issue', { args: { issueNumber: (payload as any)?.issueNumber }, commands: [gh, npm], // available only inside this skill call }); return { result }; }, }); ``` The agent can run `gh issue view 42 ...` because `gh` is in `commands`. It cannot read `GH_TOKEN` directly — the token is bound to the `gh` invocation by the runtime, not exposed to the model. ### Tasks (delegated subagents) `session.task(prompt, options?)` runs a child session with its own message history but the same sandbox/filesystem — perfect for parallel research or focused subgoals. ```ts const research = await session.task('Read /workspace/kb/billing/* and summarize the refund policy in 5 bullets.'); const reply = await session.prompt(`Use this research to answer the customer:\n\n${research}`); ``` Tasks support a `role` and `cwd` override, so you can run them as a "researcher" without changing the parent role. ### Mounting a knowledge base Combine `withFilesystemSources` with the bare `@fabric-harness/sdk` import for a compact support agent in ~25 lines: ```ts import { agent, localDirectorySource, withFilesystemSources } from '@fabric-harness/sdk'; const sandbox = withFilesystemSources('empty', [{ mountAt: '/workspace/kb', source: localDirectorySource('./knowledge-base', { include: (p) => p.endsWith('.md') }), }]); export default agent({ name: 'support', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init({ sandbox, runtime: 'stateless' })).session(); const reply = await session.skill('triager', { args: { message: String((payload as any)?.message ?? '') }, }); return { reply }; }, }); ``` See [Filesystem sources](/docs/reference/filesystem-sources) for more. ### MCP tools `connectMcpServer` works the same from the bare `@fabric-harness/sdk` import — just don't forget to `close()` the connection in a `finally`: ```ts import { connectMcpServer, agent } from '@fabric-harness/sdk'; export default agent({ name: 'gh-assist', run: async ({ init, input, env }) => { const github = await connectMcpServer('github', { url: 'https://mcp.github.com/mcp', headers: { Authorization: `Bearer ${env?.GITHUB_TOKEN}` }, }); try { const session = await (await init({ tools: github.tools, runtime: 'stateless' })).session(); return await session.prompt(String((payload as any)?.prompt ?? '')); } finally { await github.close(); } }, }); ``` ## Progressive disclosure: when to switch to /strict The minimal entrypoint can be a starting point. Add production features one by one as the work demands them: | You want to... | Switch to... | What changes in the agent | | --- | --- | --- | | Validate inputs / outputs | `agent({ input, output, run })` | Add Fabric schemas; the bare `@fabric-harness/sdk` import doesn't validate. | | Restrict shell / tools | `policy: CapabilityPolicy` on `init` or `session.prompt` | Allow/deny/requireApproval lists. | | Bind a privileged CLI | `defineCommand('gh', { env: { GH_TOKEN } })` + `commands: [gh]` | Secrets bound at the command, never exposed to the model. | | Run a Markdown skill | `session.skill('triage', { args, result })` | Drop the skill into `.fabricharness/skills/triage.md`. | | Persist conversations | `runtime: 'inline'` + a `SessionStore` (SQLite/Postgres/file from `@fabric-harness/node`) | Same `init()`, just pass `store`. | | Write durable artifacts | `session.artifact(name, content)` | Requires a store that implements `putArtifact`. | | Survive crashes / restarts | `runtime: 'temporal'` + `@fabric-harness/temporal` | The agent code is unchanged; the runtime swaps. | ## Minimal vs complete entrypoint at a glance ```ts title="Default import: 10 lines" import { agent } from '@fabric-harness/sdk'; export default agent({ name: 'echo', run: async ({ init, input }) => { const session = await (await init({ runtime: 'stateless' })).session(); return { reply: await session.prompt(String((payload as any)?.message)) }; }, }); ``` ```ts title="Strict import: typed I/O + policy + artifacts + skills" import { agent, schema } from '@fabric-harness/sdk'; import type { CapabilityPolicy } from '@fabric-harness/sdk'; const policy: CapabilityPolicy = { commandPolicy: { allow: ['git status*', 'git diff*'], deny: ['git push*'], requireApproval: ['gh issue comment**'], }, maxCommandTimeoutMs: 30_000, }; const inputSchema = schema.object({ issueNumber: schema.number(), title: schema.string() }); const outputSchema = schema.object({ severity: schema.enum(['low','medium','high']), summary: schema.string() }); export default agent({ name: 'triage', input: inputSchema, output: outputSchema, model: process.env.FABRIC_MODEL, skills: [{ name: 'triage', content: 'Triage the issue and return a concise structured result.' }], run: async ({ init, input }) => { const session = await (await init({ policy })).session(); const result = await session.skill('triage', { args: input, policy, result: outputSchema }); await session.artifact('triage.md', `# ${input.title}`, { contentType: 'text/markdown' }); return result; }, }); ``` The CLI, dev server, and deploy targets are shared. The SDK entrypoint and runtime choice are independent; see [SDK entrypoints, runtimes, and targets](/docs/reference/sdk-entrypoints-runtimes-targets). ## Anti-patterns - **Don't fork your code into minimal and complete copies.** Graduate features in place when you need typed I/O, policy, durable stores, telemetry, or provider classes. - **Don't use `runtime: 'stateless'` when you need approvals.** The approval flow needs a store to wait on; in stateless mode it falls through immediately. - **Don't import from both `@fabric-harness/sdk` and `@fabric-harness/sdk` in the same agent.** Pick one entrypoint. The complete entrypoint is the broader surface; the bare `@fabric-harness/sdk` import is the smaller import graph. - **Don't assume durability from the import path.** Use `runtime: 'temporal'` or a durable store explicitly. ## See also - [examples/minimal](https://github.com/your-org/fabric-harness/tree/main/examples/minimal) — the 10-line example end-to-end. - [examples/support-agent](https://github.com/your-org/fabric-harness/tree/main/examples/support-agent) — minimal entrypoint + filesystem-mounted knowledge base. - [SDK entrypoints, runtimes, and targets](/docs/reference/sdk-entrypoints-runtimes-targets) — how the choices fit together. - [Runtime modes](/docs/reference/runtime-modes) — `inline` vs `stateless` vs `temporal`. - [Filesystem sources](/docs/reference/filesystem-sources) — mount read-only content into the sandbox. --- # Installation Canonical: https://harness.fabric.pro/docs/getting-started/installation Install Fabric Harness from npm and verify the CLI works. import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Fabric Harness ships as a set of npm packages under the `@fabric-harness/*` scope. The CLI installs a `fabric-harness` (alias `fh`) binary; the SDK is added per-workspace. ## Prerequisites - **Node.js 22+** - **npm 10+**, **pnpm 10+**, or **yarn 4+** - *(optional)* **Docker** for the Docker sandbox - *(optional)* **Temporal** (local dev server) for the Temporal worker target - An LLM provider API key (e.g. `OPENAI_API_KEY`) when you want real model calls. ## 1. Install the CLI Install globally so `fabric-harness` and `fh` are on your PATH: ```sh npm install -g @fabric-harness/cli ``` ```sh pnpm add -g @fabric-harness/cli ``` ```sh yarn global add @fabric-harness/cli ``` ```sh fh --help ``` ## 2. Bootstrap a workspace A Fabric Harness workspace is any directory with a `.fabricharness/` folder. ```sh mkdir my-agents cd my-agents npm init -y mkdir -p .fabricharness/agents ``` ## 3. Add the SDK The bare `@fabric-harness/sdk` is enough for most agents. Add the deploy-target package only if you need it. ```sh npm install @fabric-harness/sdk ``` ```sh npm install @fabric-harness/sdk @fabric-harness/cloudflare ``` ```sh npm install @fabric-harness/sdk @fabric-harness/temporal ``` ```sh npm install @fabric-harness/sdk @fabric-harness/azure ``` > **Strict mode** (`@fabric-harness/sdk/strict`) is a sub-path export of the same package — no separate install needed. ### All published packages | Package | Purpose | |---|---| | `@fabric-harness/cli` | The `fh` / `fabric-harness` binary. | | `@fabric-harness/sdk` | Core SDK. Default + `/strict` entry points. | | `@fabric-harness/client` | Typed HTTP, SSE, and WebSocket client for jobs and persistent agents. | | `@fabric-harness/node` | Node deploy target, HTTP server, SQLite/Postgres session stores. | | `@fabric-harness/channels` | Slack, GitHub, Teams, Discord, Telegram, WhatsApp, and other channel adapters. | | `@fabric-harness/react` | React hooks and components for the Fabric Harness protocol. | | `@fabric-harness/databases` | Database integrations and discoverable database configuration helpers. | | `@fabric-harness/temporal` | Temporal worker target + activities. | | `@fabric-harness/cloudflare` | Cloudflare Worker target, R2 sources, Cloudflare Sandbox helpers. | | `@fabric-harness/azure` | Azure OpenAI provider, Key Vault secret resolver, Foundry hosted agent target. | | `@fabric-harness/connectors` | Sandbox connectors (Daytona, E2B, Modal), object-storage sources (S3, Azure Blob). | | `@fabric-harness/databricks` | Unity AI Gateway, SQL, Unity Catalog, Vector Search/RAG, Lakebase, Jobs, and Databricks Apps. | | `@fabric-harness/evals` | Evaluation harness. | ## 4. Verify Run the doctor command from your workspace: ```sh fh doctor --tools ``` To verify a real model end-to-end: ```sh echo 'OPENAI_API_KEY=sk-...' > .env.local fh doctor --live --model openai/gpt-5.5 ``` ## 5. Write your first agent ```ts title=".fabricharness/agents/echo.ts" import { agent } from '@fabric-harness/sdk'; export default agent<{ message: string }>({ name: 'echo', model: 'openai/gpt-5.5', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); return { reply: await session.prompt(input.message) }; }, }); ``` ```sh fh agents fh describe echo fh run echo --message "What is Temporal?" ``` If the configured model responds, you're ready for [your first agent](/docs/getting-started/first-agent). ## Updating ```sh npm update -g @fabric-harness/cli # CLI npm update @fabric-harness/sdk # SDK + add-ons in your workspace ``` Releases follow semver. Each package ships a `CHANGELOG.md`, and the npm registry exposes the current version and release history. --- # Migrating to 2.0 Canonical: https://harness.fabric.pro/docs/getting-started/migration-v2 Required directory, route, client, message, and persistence changes for Fabric Harness 2.0. Fabric Harness 2.0 makes persistent-agent input a durable submission and enforces the job/agent directory split. ## Required changes 1. Move finite `agent({ run })` / `job({ run })` modules from `.fabricharness/agents/` to `.fabricharness/jobs/`. 2. Invoke finite jobs with `POST /jobs/:name`. The former finite `POST /agents/:name/:id` route returns `410`. 3. Treat persistent `POST /agents/:name/:id` as admission: it returns `202 { submissionId, streamUrl, offset }` by default. 4. Use `@fabric-harness/client` to wait, observe, read offset-addressed conversation records, or abort. Add `?wait=true` only for synchronous compatibility. 5. Configure durable submission, conversation-stream, and attachment stores when work must survive process restarts. 6. Update build-manifest consumers for schema v2: finite definitions are in `jobs`, while `agents` contains persistent definitions. ## Client example ```ts import { createFabricClient } from '@fabric-harness/client'; const client = createFabricClient({ baseUrl: 'http://localhost:4317' }); const admitted = await client.agents.send({ agent: 'assistant', id: 'customer-42', message: { kind: 'user', body: 'Summarize the latest activity.' }, }); const settled = await client.agents.wait({ agent: 'assistant', id: 'customer-42', submissionId: admitted.submissionId, }); ``` Inputs accept strings for convenience or a `DeliveredMessage`: user messages can include inline or durable-ref attachments; signal messages carry typed out-of-band context. ## Databricks note The v2 Databricks preset adds app identity, UC Volumes attachments, submission telemetry, and Lakebase stores. Lakebase exchanges workspace OAuth for database credentials and supplies session, submission, and stream stores; configure the full endpoint resource name and run the live restart smoke before production. See [Databricks deployment](/docs/deployment/databricks). The repository's [complete migration guide](https://github.com/Fabric-Pro/fabric-harness/blob/main/docs/migration-v2.md) includes route details, store options, and the package version map. --- # Workspace Layout Canonical: https://harness.fabric.pro/docs/getting-started/workspace-layout The .fabricharness directory convention. Fabric Harness uses `.fabricharness/` as the embedded workspace directory. The CLI walks up from your current working directory to find the nearest workspace root. ## Directory shape ```txt .fabricharness/ jobs/ # Finite defineJob()/agent({ run }) definitions ask.ts agents/ # Persistent defineAgent() definitions support.ts roles/ # Markdown role overlays engineer.md reviewer.md skills/ # Reusable Markdown procedures triage-issue/ SKILL.md fix-tests/ SKILL.md policies/ # Optional capability/security policies default.ts production.ts sandboxes/ # Project-specific sandbox factories local.ts docker.ts sessions/ # Persisted session state (gitignore-friendly) build/ # Emitted build artifacts (gitignored) config.ts # Optional central config (model, target, temporal, ...) AGENTS.md # Repo-level agent guidelines (cross-tool standard) ``` ## What each folder does ### `jobs/` Each `.ts` file default-exports `defineJob({...})` or the lower-level `agent({...})`. Jobs have a finite `run` handler and are invoked at `POST /jobs/:name`. ### `agents/` Persistent, addressable agents default-export `defineAgent(({ id }) => ({ ... }))`. Their sessions survive individual messages and are addressed at `/agents/:name/:id`. See [Agent anatomy](/docs/building/anatomy). ### `roles/` Markdown files with optional YAML frontmatter. Roles are **system-prompt overlays**, not user messages. Precedence is **call role → session role → agent role**. ### `skills/` Each skill lives in its own directory with a `SKILL.md`. Frontmatter declares name, description, and (optionally) model. The body is the instructional prompt. ### `policies/` Optional. Capability policies that scope filesystem reads/writes, allowed commands, network egress, and required approvals. ### `sandboxes/` Project-specific factories that return a `SandboxEnv`. Use this when the built-in `local`/`docker`/`empty` sandboxes don't cover your needs. ### `sessions/` Session history, tasks, approvals, checkpoints, and artifacts persisted by the file-backed session store. SQLite and Postgres stores are also available — see [Session stores](/docs/reference/session-stores). ### `build/` Output of `fh build`. Each target produces its own subdirectory: ```txt .fabricharness/build/ node/ docker/ temporal-worker/ cloudflare/ foundry-hosted-agent/ ``` ### `config.ts` Optional. See [Configuration](/docs/getting-started/configuration). ### `AGENTS.md` A repo-level Markdown file that tools across the agent ecosystem look for. Use it to capture project-wide guidelines (preferred libraries, security expectations, never-do's). --- # Audit export Canonical: https://harness.fabric.pro/docs/operating/audit-export Stream session audit logs to JSONL, CSV, Datadog Logs, or any HTTP collector. Every state-changing action a fabric-harness session takes — model calls, tool calls, approvals, webhook deliveries, errors — lands as a `SessionEntry` in the session log. `fh export-audit` walks that log and emits one row per entry, with normalized columns. ## Local export ```sh fh export-audit # JSONL to stdout fh export-audit --format csv --output audit.csv # CSV to file ``` Pipe to `jq` for ad-hoc filtering: ```sh fh export-audit ask-1f4f... | jq -c 'select(.type=="model_attempt") | {model, costUsd}' ``` ## Datadog Logs Ship straight to the Datadog HTTP intake: ```sh fh export-audit \ --to datadog \ --datadog-api-key env://DATADOG_API_KEY \ --datadog-site us5.datadoghq.com \ --service my-saas ``` - Batches in groups of 500 rows. - Adds `ddsource: 'fabric-harness'`, `service`, and `ddtags: 'session:,tenant:'`. - Defaults `--datadog-site` to `datadoghq.com` (US1). ## Splunk HEC ```sh fh export-audit \ --to splunk \ --splunk-url https://splunk.example.com:8088/services/collector/event \ --splunk-token env://SPLUNK_HEC_TOKEN \ --splunk-source fabric-harness \ --splunk-sourcetype fabric:audit ``` Each row is wrapped in a Splunk HEC envelope (`{ event, source, sourcetype, time }`) and POSTed in batches of 200. The `time` field uses `entry.timestamp` (epoch seconds) so Splunk indexes events at their original time. ## BigQuery ```sh fh export-audit \ --to bigquery \ --bigquery-project my-gcp-project \ --bigquery-dataset audit \ --bigquery-table fabric_harness \ --gcp-access-token env://GCP_TOKEN ``` Uses BigQuery's `tabledata.insertAll` REST endpoint. Caller supplies a pre-issued OAuth access token (Workload Identity, ADC, GCS impersonation, etc.) — fabric-harness doesn't bake in `google-auth-library`. Batches at 500 rows per request; each row gets a deterministic `insertId` so retries deduplicate cleanly. Schema in BigQuery should match the audit row shape; missing columns are tolerated via `ignoreUnknownValues: true`. ## Generic HTTP collector For Splunk HEC, Logflare, an internal NDJSON ingestor, etc., point `--to http`: ```sh fh export-audit \ --to http \ --url https://collector.internal/audit \ --auth-token env://COLLECTOR_TOKEN ``` Posts a single NDJSON body (`application/x-ndjson`) with each row as one line. Bearer-token auth is optional. ## Row shape Every row carries these columns when populated: | Column | Source | |---|---| | `timestamp`, `type`, `sessionId`, `entryId`, `parentId` | session entry envelope | | `tenantId` | session-level tenant id | | `tool`, `command`, `model`, `provider`, `durationMs` | per-entry metadata | | `inputTokens`, `outputTokens`, `cachedInputTokens`, `cacheWriteTokens`, `costUsd` | usage from `model_attempt` entries | | `approvalId`, `audience` | approval lifecycle entries | | `eventType`, `idempotencyKey` | webhook deliveries | | `errorMessage` | error entries | Streams line-by-line — handles large sessions without buffering. ## Backfill / replay `fh export-audit` reads from any configured `SessionStore` — file-backed, SQLite, or Postgres. Run it as a periodic job to push the last hour's sessions to your SIEM, or on-demand when investigating an incident. --- # Cost Attribution Canonical: https://harness.fabric.pro/docs/operating/cost-attribution Enforce budgets against real Databricks spend and attribute cost by agent, user, and tenant. The SDK cost-budget tracks per-call USD from a static price table — good for immediate, synchronous protection, but it doesn't know your **real** Databricks spend (model serving, SQL, Vector Search, AI Functions, Genie, Lakeflow, Feature Serving — all measured in DBUs in System Tables). Cost reconciliation closes that gap: enforce `perScope` budgets against actuals while keeping estimates for `perCall`/`perSession`. ## Hybrid enforcement The key idea maps the existing limit tiers to what each can know: - **`perCall` / `perSession`** stay estimate-based → immediate, synchronous soft caps. They catch a runaway agent **now**. - **`perScope`** (tenant / agent / user) becomes the real-spend hard cap via an external `ActualCostSource` → retroactive, cross-process. It catches sustained overspend on the **next** call. System Tables lag 15–60 minutes, so actual enforcement is intentionally lagged — but the estimate tiers cover the window. A model call is **never** blocked on a warehouse query: actuals are fetched asynchronously after the response, cached (default 60s), and a violation is enforced before the next call. ## Databricks tenant limit ```ts import { databricksTenantCostLimit } from '@fabric-harness/databricks'; const costLimit = databricksTenantCostLimit(client, warehouseId, tenantId, { perDayUsd: 50, cacheTtlMs: 60_000, }); const fabric = await init({ costLimit /* …model, tools, policy */ }); ``` This returns a `CostLimit` with `scopeSource: 'external'` and a Databricks `ActualCostSource` that queries `system.billing.usage` joined to `system.billing.list_prices`, filtered by `fabric.*` custom tags, cached per scope key. Existing `tenantCostLimit` / `postgresCostBudgetStore` usage is unchanged — `scopeSource` defaults to `incremental`. ## Attribution dimensions Spend is attributed across **agent · user · tenant · model · provider · session · turn**. Stamped onto each `ModelUsage` and tool-call entry, these answer "which agent/user/tenant spent what": ```ts const breakdown = await databricksActualCostSource(client, warehouseId).queryAttribution({ tenantId: 'acme', startDate: '2026-06-01', endDate: '2026-06-23', groupBy: ['agentId', 'userId'], }); // → [{ agentId, userId, tenantId, period, estimatedUsd, actualUsd, actualDbus, deltaUsd, calls }] ``` `queryAttribution` is for offline/ops/dashboard use (it may scan large windows), not inline enforcement. `cost_limit` events carry `actualsFetchedAt` / `cacheTtlMs` so consumers understand the lag. ## Attribution contract Attributing spend to a specific agent/tenant requires Databricks resources to carry `fabric.*` custom tags (which flow into `system.billing.usage.custom_tags`). The Databricks deploy targets can tag the resources they create; for untagged / bring-your-own setups, scope coarsely by warehouse/endpoint + time window. ## See also - [Multi-tenancy](/docs/operating/multi-tenancy) · [Databricks](/docs/deployment/databricks) - Example: `examples/with-databricks-cost-attribution` (runs against a mocked warehouse). --- # Secrets, Retention, and Residency Canonical: https://harness.fabric.pro/docs/operating/data-governance Runtime secret providers, enforceable data retention, residency checks, and signed deletion evidence. ## Runtime secret providers Commands use `secret('NAME')` references. The model and session log see the reference name, never the resolved value. Compose providers in deployment order and pass one resolver into the agent: ```ts import { chainSecretProviders, environmentSecretProvider, secretResolver } from '@fabric-harness/sdk'; import { vaultSecretProvider } from '@fabric-harness/node'; const secrets = chainSecretProviders( environmentSecretProvider({ allow: ['LOCAL_TOKEN'] }), vaultSecretProvider({ address: process.env.VAULT_ADDR!, token: () => workloadIdentityVaultToken(), namespace: 'platform', pathForSecret: (name, tenantId) => `tenants/${tenantId}/${name}`, }), ); const fabric = await init({ resolveSecret: secretResolver(secrets) }); ``` Provider failures stop resolution instead of falling through to a less-governed provider. Use workload identity or a rotating token callback rather than embedding a provider credential in source. ### Azure Key Vault ```ts import { azureKeyVaultSecretProvider } from '@fabric-harness/azure'; const keyVault = azureKeyVaultSecretProvider({ vaultUrl: 'https://my-vault.vault.azure.net', token: () => managedIdentityAccessToken(), }); ``` ### Databricks Secrets ```ts import { databricksSecretsProvider } from '@fabric-harness/databricks'; const workspaceSecrets = databricksSecretsProvider({ host: process.env.DATABRICKS_HOST!, token: () => appServicePrincipalToken(), scope: (_name, tenantId) => `fabric-${tenantId}`, }); ``` The Databricks adapter calls Secret Management at runtime and decodes the returned value inside the provider closure. Workspace tokens and secret values are not added to model messages, events, audit entries, or telemetry. ## Retention policy `enforcePersistenceRetention()` applies independent age limits to sessions, conversation streams, attachments, audit storage, and telemetry storage. External audit and telemetry systems provide a `deleteBefore()` adapter, so a configured rule cannot silently report success without deleting from that system. ```ts import { enforcePersistenceRetention } from '@fabric-harness/node'; const result = await enforcePersistenceRetention(persistence, { sessions: { maxAgeDays: 30 }, conversationStreams: { maxAgeDays: 14 }, attachments: { maxAgeDays: 7 }, audit: { maxAgeDays: 365, target: siemRetentionTarget }, telemetry: { maxAgeDays: 30, target: tracingRetentionTarget }, residency: { region: 'us-west-2', allowedRegions: ['us-west-2'] }, }); ``` Run the coordinator from a singleton scheduled job. Session expiry uses the bundle's cascade deletion path, which removes submissions, conversation streams, and attachments together. Attachment timestamps are used when available; older stored references inherit their session update time. ## Residency `assertDataResidency(region, allowedRegions)` fails before data maintenance or export when the deployment region is not allowed. Use the same configured region for the database, object backup location, audit sink, telemetry sink, and Databricks workspace. Region labels are application policy inputs; cloud network and IAM controls must independently prevent cross-region endpoints. ## Signed deletion evidence Wrap a persistence bundle to emit an append-only completion record after each successful cascade deletion: ```ts import { hmacDeletionEvidenceSigner, postgresDeletionEvidenceStore, postgresPersistence, withDeletionEvidence, } from '@fabric-harness/node'; const base = postgresPersistence({ client: pool }); const persistence = withDeletionEvidence(base, { signer: hmacDeletionEvidenceSigner({ key: process.env.DELETION_EVIDENCE_KEY!, keyId: 'retention-2026-01', }), store: postgresDeletionEvidenceStore(pool), }); ``` The receipt contains deletion counts, completion time, key id, signature, and a digest chained to the previous record. Session and tenant identifiers are stored only as keyed HMAC pseudonyms. Prompts, messages, filenames, attachment bytes, and other deleted content are never copied into the evidence record. Use `ed25519DeletionEvidenceSigner()` when verifiers must validate records without access to the signing key. --- # Backup and Disaster Recovery Canonical: https://harness.fabric.pro/docs/operating/disaster-recovery Versioned Postgres and Lakebase migrations, object backups, readiness, and recovery drills. Fabric Harness uses the same migration and recovery path for PostgreSQL and Databricks Lakebase. Migrations are ordered, recorded in `fabric_harness_schema_migrations`, and serialized with a PostgreSQL advisory lock. Each version runs in its own transaction. ```ts import { postgresPersistence } from '@fabric-harness/node'; const persistence = postgresPersistence({ client: pool }); await persistence.migrate(); ``` Calling `migrate()` from several replicas is safe. Exactly one connection owns the migration lock, while the other replicas wait and then read the completed ledger. Rollback is available only for versions with an explicit `down` migration; Fabric Harness refuses a destructive implicit rollback. ## Object backup The logical backup runs in a `REPEATABLE READ`, read-only transaction. Binary attachments and artifacts are encoded without losing bytes. The complete payload receives a SHA-256 digest before it is sent to object storage. ```ts import { backupPostgresPersistence, httpBackupObjectStore, } from '@fabric-harness/node'; const objectStore = httpBackupObjectStore({ // Return a short-lived S3/R2 presigned URL, Azure Blob SAS URL, or an // authenticated internal object-gateway URL for this key. urlForKey: (key) => backupUrlFor(key), headers: async (method, key) => backupHeadersFor(method, key), }); const backup = await backupPostgresPersistence({ client: pool, objectStore, objectKey: `production/${new Date().toISOString()}.json`, }); console.log(backup.sha256, backup.tables, backup.bytes); ``` Lakebase uses the same call after exchanging the Databricks workspace identity for a short-lived database credential. Workspace tokens and database passwords are never written into the backup object. ## Restore Restore verifies the digest and exact Fabric Harness table set before changing the database. It then takes an exclusive restore lock, truncates the managed tables, restores every row in one transaction, and resets identity sequences. ```ts import { restorePostgresPersistence } from '@fabric-harness/node'; await restorePostgresPersistence({ client: pool, objectStore, objectKey: 'production/2026-07-09T08:00:00.000Z.json', }); ``` Stop application writes before restore. Keep the API unready until restore and post-restore verification finish. ## Readiness `/ready` checks the session and submission stores. A configured persistence bundle also contributes its health result. Add worker and provider dependencies with `readiness`: ```ts await startDevServer({ readiness: async () => ({ temporalWorker: { ok: await temporalWorkerIsPolling() }, modelServing: { ok: await servingEndpointIsReady() }, }), }); ``` Any failed or rejected dependency returns HTTP `503`, so Kubernetes, Databricks Apps, and load balancers stop routing work to an incomplete replica. ## Recovery targets A practical production starting point is: | Workload | Backup interval | Reference RPO | Reference RTO | | --- | ---: | ---: | ---: | | Human-facing agents | 5 minutes | 5 minutes | 30 minutes | | High-value automation | 1 minute plus database WAL/PITR | 1 minute | 15 minutes | | Development | Daily | 24 hours | 2 hours | Use provider-native WAL or point-in-time recovery in addition to Fabric Harness logical backups when the required RPO is below the backup interval. Exercise the application-level path with `runPostgresRecoveryDrill()` in an isolated database. It creates an object backup, invokes your failure simulation, restores, runs your verification callback, and fails when `maxRpoSeconds` or `maxRtoMs` is exceeded. ```ts const result = await runPostgresRecoveryDrill({ client: drillPool, objectStore, objectKey: `drills/${Date.now()}.json`, maxRpoSeconds: 300, maxRtoMs: 30 * 60_000, simulateFailure: () => destroyDrillDatabaseState(), verify: () => verifyReferenceSessionsAndAttachments(), }); ``` --- # Multi-tenancy Canonical: https://harness.fabric.pro/docs/operating/multi-tenancy Stamp tenant ids on sessions, scope listings and reads per tenant, and gate webhook deliveries by tenant header. fabric-harness exposes a single concept for multi-tenancy: an opaque `tenantId` string. fabric-harness never interprets it — host applications map it to whatever identity model they own (organizations, customers, projects, workspaces). What fabric-harness provides: - Stamping: every entry, event, and approval emitted by a session carries `tenantId`. - Cross-tenant guard: appending to a session that belongs to a different tenant throws. - Listing filter: `listSessionSummariesFromStore(store, { tenantId })` returns only that tenant's sessions. - Server scoping: `fh server` reads `X-Fabric-Tenant` (or `?tenant=`) and applies the filter + 404s on cross-tenant reads. ## Stamping a session ```ts const fabric = await init({ tenantId: 'tenant-acme', // ...other options }); const session = await fabric.session(); // tenantId inherited // or override per-session: const ad = await fabric.session(undefined, { tenantId: 'tenant-globex' }); ``` `tenantId` propagates from `AgentInit` → `SessionOptions` → every `SessionEntry`, `FabricEvent`, and `ApprovalRequest` the session emits. ## Cross-tenant guard If two `init()` calls (possibly from different hosts) target the same session id but disagree on the tenant, the second one throws `POLICY_DENIED`. This catches accidental cross-tenant writes early — better than silent corruption. ## CLI ```sh fh sessions --tenant tenant-acme # filter listing fh run hello --tenant tenant-acme # stamp on the spawned session fh export-audit # tenantId column appears on every row ``` ## HTTP server ```sh curl -H 'X-Fabric-Tenant: tenant-acme' http://localhost:9111/sessions curl -H 'X-Fabric-Tenant: tenant-acme' -d '{"text":"hi"}' \ http://localhost:9111/agents/edge-summarizer/$(uuidgen) ``` - Listing: only sessions stamped with the matching tenant are returned. - Read: a 404 is returned when the loaded session's tenant differs from the header (no information leak about session existence). - Write: spawned session inherits the header's tenantId. Set `FABRIC_HARNESS_TENANT_REQUIRED=1` to reject any request that omits the header. ## Identity layer is yours fabric-harness deliberately doesn't ship a "tenants table" or auth integration. Bring your own: - Map `X-Fabric-Tenant` to your SaaS tenant ids in your reverse proxy. - Enforce per-tenant cost ceilings via `costLimit.perScope + scopeKey`. Use `tenantId` as the scope key. - Combine with `approvalRules.audience` — emit approvals to per-tenant audience channels (`reviewer:tenant-acme`). ## See also - [Cost budgets → Cross-process aggregation](/docs/building/model-providers#cross-process-aggregation) - [Audit export](/docs/operating/audit-export) - [Rate limiting](/docs/operating/rate-limiting) --- # Operational SLOs Canonical: https://harness.fabric.pro/docs/operating/operational-slos Stable runtime metrics, SLO evaluation, alerts, dashboards, and cross-target conformance. Fabric Harness defines low-cardinality metrics for the operational signals that matter across Node, Temporal, Cloudflare, and Databricks Apps: | Metric | Meaning | | --- | --- | | `fabric_harness_request_latency_ms` | End-to-end HTTP or application request latency | | `fabric_harness_submission_duration_ms` | Admission-to-settlement duration | | `fabric_harness_queue_age_ms` | Admission-to-worker-start delay | | `fabric_harness_errors_total` | Failed requests, submissions, events, and recovery attempts | | `fabric_harness_approval_wait_ms` | Approval request-to-terminal-decision delay | | `fabric_harness_recovery_duration_ms` | Worker or persistence recovery duration | | `fabric_harness_cost_usd_total` | Attributed model and serving cost | ## Collect and export ```ts import { createOperationalMetricsCollector } from '@fabric-harness/sdk'; const metrics = createOperationalMetricsCollector(); const config = { onEvent: (event) => metrics.recordEvent(event), submissionTelemetry: metrics.submissionSink, }; application.get('/metrics', () => new Response(metrics.openMetrics(), { headers: { 'content-type': 'application/openmetrics-text; version=1.0.0' }, })); ``` Call `recordRequest(durationMs, outcome)` at the HTTP or application boundary and `recordRecovery(durationMs, recovered)` around recovery drills. The collector keeps bounded samples and exports count, sum, p50, p95, and p99. ## Evaluate a service objective ```ts import { evaluateOperationalSlos } from '@fabric-harness/sdk'; const evaluation = evaluateOperationalSlos(metrics.snapshot(), { requestLatencyP95Ms: 2_000, queueAgeP95Ms: 5_000, errorRateMax: 0.01, approvalWaitP95Ms: 4 * 60 * 60_000, recoveryP95Ms: 60_000, costUsdMax: 1_000, }); ``` The runnable `examples/operational-slos` workspace includes an importable Grafana dashboard and Prometheus alert rules for every metric above. Adjust the reference thresholds to the user journey and recovery tier of each deployment. ## Deployment conformance `runDeploymentConformance()` is the same black-box client suite for every deployment target. It checks health, dependency readiness, asynchronous finite job settlement and event offsets, plus persistent-agent submission settlement and conversation offsets. ```ts import { assertDeploymentConformance } from '@fabric-harness/client'; await assertDeploymentConformance({ target: 'databricks-app', baseUrl: process.env.DATABRICKS_APP_URL!, headers: { authorization: `Bearer ${process.env.APP_TOKEN}` }, job: { name: 'analyst', input: { question: 'SELECT 1' } }, }); ``` The live workflow runs this contract against a local Node host, a real Temporal cluster behind the Node HTTP API, local and deployed Cloudflare Workers, and the reference Databricks App. Each run writes a redacted JSON evidence artifact. --- # Rate limiting Canonical: https://harness.fabric.pro/docs/operating/rate-limiting Token-bucket throttling for outbound provider calls, keyed per API key. When many sessions / agents share a process, they can stampede a single provider API key — bursting past quota and triggering 429s. fabric-harness ships a generic token-bucket primitive that throttles outbound calls per key. ## Configure on a provider ```ts import { OpenAICompatibleModelProvider, tokenBucketRateLimiter } from '@fabric-harness/sdk'; const limiter = tokenBucketRateLimiter({ tokensPerSecond: 50, // sustained throughput burst: 200, // peak capacity }); const provider = new OpenAICompatibleModelProvider({ baseUrl: 'https://api.openai.com/v1', apiKey: process.env.OPENAI_API_KEY!, defaultModel: 'gpt-4o', rateLimiter: limiter, }); ``` `OpenAICompatibleModelProvider` and `AnthropicModelProvider` accept `rateLimiter`. Each call (`generate` and `stream`) calls `await limiter.acquire()` before sending the request. ## Bucket keys The provider hashes the API key into a short opaque string (`:`) and uses that as the bucket key. Practical implications: - Two providers using the same API key share the same bucket — quota stays honest. - Two providers with different API keys (e.g. OpenAI Tier 4 + Tier 1) get separate buckets automatically. - Raw keys never appear in logs or metric labels — only the hash prefix. ## Reusing the limiter elsewhere `tokenBucketRateLimiter()` returns a generic `RateLimiter`. Use it inside connectors, webhook fan-out, or anywhere you want simple throttling: ```ts const httpLimiter = tokenBucketRateLimiter({ tokensPerSecond: 10, burst: 20 }); async function callExternalApi(url: string) { await httpLimiter.acquire(`api:${new URL(url).host}`); return fetch(url); } ``` Aborts mid-wait when an `AbortSignal` is passed to `acquire({ signal })`. ## Cross-process throttling (Redis) `@fabric-harness/node` ships `redisRateLimiter()` for fleets that share an API key — multiple containers, autoscaled workers, multi-region. Same `RateLimiter` interface, atomic Lua-script refill on the Redis side. ```ts import { OpenAICompatibleModelProvider } from '@fabric-harness/sdk'; import { redisRateLimiter } from '@fabric-harness/node'; import Redis from 'ioredis'; // or @upstash/redis — only `eval` is required const limiter = redisRateLimiter({ client: new Redis(process.env.REDIS_URL!), tokensPerSecond: 100, burst: 500, keyPrefix: 'fh:rl', // optional; defaults to fh:rl }); const provider = new OpenAICompatibleModelProvider({ baseUrl: 'https://api.openai.com/v1', apiKey: process.env.OPENAI_API_KEY!, rateLimiter: limiter, }); ``` **Compatible clients:** anything that exposes `eval(script, keys, args)` matching the standard Redis Lua surface. Tested against `ioredis` and `@upstash/redis`. fabric-harness has no dependency on either — bring your own. **Atomicity:** every `acquire` runs a single Lua script that reads the bucket, refills based on elapsed time, and either consumes tokens or returns the wait duration. No race window. **Bucket TTL:** Redis keys auto-expire 60s after the bucket would be fully refilled, so abandoned bucket keys (e.g. one-off API keys) eventually disappear. ## See also - [Cost budgets](/docs/building/model-providers#spend-caps) — for USD ceilings - [Multi-tenancy](/docs/operating/multi-tenancy) --- # Supply-chain Evidence Canonical: https://harness.fabric.pro/docs/operating/supply-chain Release SBOMs, licenses, NOTICE review, vulnerability and secret scans, digests, provenance, and attestations. Every Fabric Harness release tag runs the `Release supply-chain evidence` workflow. High or critical dependency advisories, a secret finding, a package without Apache-2.0 metadata, an incompatible dependency license, a missing NOTICE file, an import failure, or a digest mismatch blocks the release. Run the same evidence generation locally: ```sh pnpm run build pnpm run check:supply-chain ``` The command writes: | Artifact | Contents | | --- | --- | | `release-packages/*.tgz` | Exact npm package subjects used for attestation | | `release-sbom.cdx.json` | CycloneDX 1.5 component inventory | | `release-license-report.json` | Dependency versions, licenses, and package URLs | | `release-notice-review.json` | Per-package NOTICE digest and attribution review | | `release-secret-scan.json` | Packed-package secret-pattern scan result | | `release-vulnerability-report.json` | Complete `pnpm audit` result and severity totals | | `release-provenance.intoto.json` | In-toto statement with SLSA v1 build predicate | | `release-artifact-digests.json` | SHA-256 for every package subject and evidence file | `verify-supply-chain-evidence.mjs` recalculates every digest. CI also runs Gitleaks across repository history. On release tags, GitHub's OIDC identity signs build-provenance and SBOM attestations for the retained tarballs through `actions/attest-build-provenance` and `actions/attest-sbom`. Evidence artifacts are retained for 90 days in GitHub Actions. Copy them to the organization's immutable release archive when policy requires longer retention. npm packages also publish with `publishConfig.provenance: true`, so registry consumers can verify the npm provenance statement independently. ## Verify a release Download the release evidence artifact, then run: ```sh node scripts/verify-supply-chain-evidence.mjs gh attestation verify artifacts/release-packages/*.tgz \ --repo Fabric-Pro/fabric-harness ``` The first command verifies local SHA-256 integrity. The second verifies the keyless GitHub attestation identity and repository binding. --- # API reference Canonical: https://harness.fabric.pro/docs/reference/api Generated public API inventory for every published Fabric Harness entrypoint. {/* Generated by scripts/generate-api-reference.mjs. Do not edit by hand. */} This reference is generated from the declaration files shipped in each package. Import from the entrypoint shown in the table; symbols marked `type` are TypeScript-only exports. ## @fabric-harness/azure ### `@fabric-harness/azure` | Export | Kind | Summary | | --- | --- | --- | | `azureAgent` | `value` | Public export. | | `AzureAgentOptions` | `type` | Public export. | | `AzureAksClusterRef` | `type` | Public export. | | `azureAksRunCommandTool` | `value` | Public export. | | `AzureArmClient` | `value` | Public export. | | `AzureArmClientOptions` | `type` | Public export. | | `AzureBlobArtifactStore` | `type` | Public export. | | `AzureBlobArtifactStoreOptions` | `type` | Public export. | | `AzureBundle` | `type` | Public export. | | `AzureBundleConfig` | `type` | Public export. | | `AzureContainerAppsJobRef` | `type` | Public export. | | `azureContainerAppsJobTool` | `value` | Public export. | | `azureContainerInstanceExecTool` | `value` | Public export. | | `AzureContainerInstanceRef` | `type` | Public export. | | `azureKeyVaultSecretProvider` | `value` | Key Vault adapter for `chainSecretProviders()` and `secretResolver()`. | | `AzureKeyVaultSecretResolverOptions` | `type` | Public export. | | `AzureOpenAIModelProvider` | `value` | Public export. | | `AzureOpenAIModelProviderOptions` | `type` | Public export. | | `AzureResourceRef` | `type` | Public export. | | `createAzureArmClient` | `value` | Public export. | | `createAzureBlobArtifactStore` | `value` | Public export. | | `createAzureKeyVaultSecretResolver` | `value` | Public export. | | `createFoundryAgentServiceClient` | `value` | Public export. | | `defineAzureAgent` | `value` | Public export. | | `DefineAzureAgentOptions` | `type` | Public export. | | `FoundryAgentDefinition` | `type` | Public export. | | `FoundryAgentInvocationOptions` | `type` | Public export. | | `FoundryAgentInvocationResult` | `type` | Public export. | | `foundryAgentLifecycleTools` | `value` | Public export. | | `FoundryAgentServiceClient` | `value` | Public export. | | `FoundryAgentServiceOptions` | `type` | Public export. | | `foundryAgentTool` | `value` | Public export. | | `FoundryHostedAgentSandboxOptions` | `type` | Public export. | | `FoundryRuntimeModelProvider` | `value` | Model provider that calls the **Foundry-managed** Azure OpenAI surface using a Bearer token (managed identity) instead of an API key. Designed for the Azure AI Foundry Hosted Agent runtime, but useful on any Azure compute with a managed identity (ACA / AKS / VM) — you don't need the Foundry runtime adapter to use this provider. The runtime injects: - `AZURE_OPENAI_ENDPOINT` — the Foundry-routed Azure OpenAI endpoint. - `AZURE_OPENAI_DEPLOYMENT` — the model deployment name. - `FOUNDRY_AGENT_TOKEN` (or `AZURE_AI_FOUNDRY_TOKEN`) — a pre-issued token when running inside the Hosted Agent contain... | | `FoundryRuntimeModelProviderOptions` | `type` | Public export. | | `FoundryThreadMessage` | `type` | Public export. | | `FoundryTokenResolver` | `type` | Token resolver — returns a Bearer token for the Foundry runtime's managed Azure OpenAI surface. Implementations may cache and refresh. | | `MockAzureModelProvider` | `value` | A deterministic `ModelProvider` for Azure agent tests and init templates. Returns structured responses without requiring real Azure credentials. | | `MockAzureModelProviderOptions` | `type` | Public export. | | `resolveToolRefs` | `value` | Public export. | ### `@fabric-harness/azure/aks-sandbox` | Export | Kind | Summary | | --- | --- | --- | | `aksSandbox` | `value` | Public export. | | `AksSandboxOptions` | `type` | AKS-flavored Kubernetes sandbox. Pulls cluster credentials from the AKS `listClusterUserCredentials` endpoint, builds a `@kubernetes/client-node` `KubeConfig`, and delegates to `kubernetesSandbox`. Requires both peer deps: ```sh npm install | ### `@fabric-harness/azure/app-insights` | Export | Kind | Summary | | --- | --- | --- | | `ApplicationInsightsClientLike` | `type` | Optional Azure Monitor / Application Insights telemetry exporter. Adapts Fabric's `TelemetrySpan` shape into App Insights `TelemetryClient` `trackDependency` calls. The Azure Monitor SDK is provided by the caller — we don't take a hard dependency. Install peer dep: See the package declarations for an example. Usage: See the package declarations for an example. | | `applicationInsightsExporter` | `value` | Public export. | | `ApplicationInsightsExporterOptions` | `type` | Public export. | ### `@fabric-harness/azure/foundry-runtime` | Export | Kind | Summary | | --- | --- | --- | | `FoundryRuntimeModelProvider` | `value` | Model provider that calls the **Foundry-managed** Azure OpenAI surface using a Bearer token (managed identity) instead of an API key. Designed for the Azure AI Foundry Hosted Agent runtime, but useful on any Azure compute with a managed identity (ACA / AKS / VM) — you don't need the Foundry runtime adapter to use this provider. The runtime injects: - `AZURE_OPENAI_ENDPOINT` — the Foundry-routed Azure OpenAI endpoint. - `AZURE_OPENAI_DEPLOYMENT` — the model deployment name. - `FOUNDRY_AGENT_TOKEN` (or `AZURE_AI_FOUNDRY_TOKEN`) — a pre-issued token when running inside the Hosted Agent contain... | | `FoundryRuntimeModelProviderOptions` | `type` | Public export. | | `FoundryTokenResolver` | `type` | Token resolver — returns a Bearer token for the Foundry runtime's managed Azure OpenAI surface. Implementations may cache and refresh. | ### `@fabric-harness/azure/agent` | Export | Kind | Summary | | --- | --- | --- | | `azure` | `value` | Bundle factory for Azure agents. Returns an `AzureBundle` with a model provider, Azure-specific tools, and a safe default egress policy. | | `azureAgent` | `value` | Public export. | | `AzureAgentOptions` | `type` | Public export. | | `AzureBundle` | `type` | Public export. | | `AzureBundleConfig` | `type` | Public export. | | `defineAzureAgent` | `value` | Public export. | | `DefineAzureAgentOptions` | `type` | Public export. | | `resolveToolRefs` | `value` | Public export. | ## @fabric-harness/channels ### `@fabric-harness/channels` | Export | Kind | Summary | | --- | --- | --- | | `bytesToHex` | `value` | Public export. | | `Channel` | `type` | Public export. | | `channelCompatibility` | `value` | Published channel compatibility contract. Provider payload changes that only add fields are supported without a Fabric release; breaking provider versions are added here before becoming the default. | | `ChannelCompatibility` | `type` | Public export. | | `channelCompatibilityPolicy` | `value` | Public export. | | `ChannelCompatibilityStatus` | `type` | Public export. | | `ChannelContext` | `type` | Public export. | | `ChannelDispatch` | `type` | Public export. | | `ChannelDispatchRequest` | `type` | Public export. | | `ChannelRoute` | `type` | Channels turn platform webhooks (Slack, GitHub, …) into agent dispatches. Handlers are written against the Web `Request`/`Response` API and `crypto.subtle`, so the same channel runs on Node and Cloudflare. A channel is a stateless route container plus a conversation-id (de)serializer — session continuity falls out of the key (same thread → same key → same session). | | `conversationKey` | `value` | Public export. | | `defineChannel` | `value` | Validates and brands a channel's routes. | | `FirstPartyChannelName` | `type` | Public export. | | `hexToBytes` | `value` | Public export. | | `hmacSha256` | `value` | Public export. | | `parseConversationKey` | `value` | Public export. | | `ParsedConversationKey` | `type` | Public export. | | `readRequestBody` | `value` | Reads the full request body as bytes, or returns undefined if it exceeds `limitBytes`. NOTE: this consumes the request stream (single read). Signature-verifying channels need the *exact* bytes for HMAC and the parsed JSON afterward — don't call `request.json()` as well. Use `readJsonBody` to get both from one read. | | `verifyHmacSha256` | `value` | Constant-time HMAC-SHA256 verification (via `crypto.subtle.verify`). | ### `@fabric-harness/channels/compatibility` | Export | Kind | Summary | | --- | --- | --- | | `channelCompatibility` | `value` | Published channel compatibility contract. Provider payload changes that only add fields are supported without a Fabric release; breaking provider versions are added here before becoming the default. | | `ChannelCompatibility` | `type` | Public export. | | `channelCompatibilityPolicy` | `value` | Public export. | | `ChannelCompatibilityStatus` | `type` | Public export. | | `FirstPartyChannelName` | `type` | Public export. | ### `@fabric-harness/channels/slack` | Export | Kind | Summary | | --- | --- | --- | | `createSlackChannel` | `value` | Slack events channel. Verifies the `v0` HMAC signature, answers the URL-verification challenge, and dispatches `app_mention` / threaded `message` events to a persistent agent keyed by the Slack thread — with the Slack `event_id` as the dedupe key (exactly-once), the team as tenant, and the user as the acting `actor` (which is what powers on-behalf-of governance downstream). | | `parseSlackConversationKey` | `value` | Parse a Slack instance id back into the thread ref (e.g. to bind `replyInSlackThread` at agent init). | | `replyInSlackThread` | `value` | Outbound tool: reply in the Slack thread this agent is handling. | | `SlackChannel` | `type` | Public export. | | `SlackChannelOptions` | `type` | Public export. | | `slackConversationKey` | `value` | Serialize a Slack thread into the stable instance id used by `createSlackChannel`. | | `SlackEvent` | `type` | Public export. | | `SlackEventsPayload` | `type` | Public export. | | `SlackThreadRef` | `type` | Public export. | ### `@fabric-harness/channels/github` | Export | Kind | Summary | | --- | --- | --- | | `commentOnGitHubIssue` | `value` | Outbound tool: comment on the GitHub issue / PR this agent is handling. | | `createGitHubChannel` | `value` | GitHub webhook channel. Verifies the `X-Hub-Signature-256` (`sha256=<hex>` HMAC over the raw body), and dispatches issue / PR / comment events to a persistent agent keyed by `owner/repo/<kind>/<number>` — with `X-GitHub-Delivery` as the dedupe key (exactly-once), the owner as tenant, and the sender as the acting `actor`. Bot senders are ignored to avoid loops; `ping` is acknowledged. | | `GitHubChannel` | `type` | Public export. | | `GitHubChannelOptions` | `type` | Public export. | | `githubConversationKey` | `value` | Public export. | | `GitHubIssueRef` | `type` | Public export. | | `GitHubWebhookPayload` | `type` | Public export. | | `NormalizedGitHubEvent` | `type` | Public export. | | `normalizeGitHubEvent` | `value` | Public export. | | `parseGitHubConversationKey` | `value` | Public export. | ### `@fabric-harness/channels/discord` | Export | Kind | Summary | | --- | --- | --- | | `createDiscordChannel` | `value` | Public export. | | `DiscordChannel` | `type` | Public export. | | `DiscordChannelOptions` | `type` | Public export. | | `discordConversationKey` | `value` | Public export. | | `DiscordConversationRef` | `type` | Public export. | | `DiscordInteractionPayload` | `type` | Public export. | | `parseDiscordConversationKey` | `value` | Public export. | | `replyInDiscord` | `value` | Public export. | | `verifyDiscordSignature` | `value` | Public export. | ### `@fabric-harness/channels/teams` | Export | Kind | Summary | | --- | --- | --- | | `BotFrameworkAuthenticatorOptions` | `type` | Public export. | | `createBotFrameworkAuthenticator` | `value` | Verify Microsoft Bot Connector signatures, issuer, audience, lifetime, service URL, and endorsement. | | `createTeamsChannel` | `value` | Public export. | | `parseTeamsConversationKey` | `value` | Public export. | | `replyInTeamsConversation` | `value` | Public export. | | `TeamsActivityPayload` | `type` | Public export. | | `TeamsChannel` | `type` | Public export. | | `TeamsChannelOptions` | `type` | Public export. | | `teamsConversationKey` | `value` | Public export. | | `TeamsConversationRef` | `type` | Public export. | ### `@fabric-harness/channels/telegram` | Export | Kind | Summary | | --- | --- | --- | | `createTelegramChannel` | `value` | Public export. | | `parseTelegramConversationKey` | `value` | Public export. | | `replyInTelegram` | `value` | Public export. | | `TelegramChannel` | `type` | Public export. | | `TelegramChannelOptions` | `type` | Public export. | | `telegramConversationKey` | `value` | Public export. | | `TelegramConversationRef` | `type` | Public export. | | `TelegramMessagePayload` | `type` | Public export. | | `TelegramUpdatePayload` | `type` | Public export. | ### `@fabric-harness/channels/twilio` | Export | Kind | Summary | | --- | --- | --- | | `createTwilioChannel` | `value` | Public export. | | `parseTwilioConversationKey` | `value` | Public export. | | `replyWithTwilio` | `value` | Public export. | | `TwilioChannel` | `type` | Public export. | | `TwilioChannelOptions` | `type` | Public export. | | `twilioConversationKey` | `value` | Public export. | | `TwilioConversationRef` | `type` | Public export. | | `TwilioMessagePayload` | `type` | Public export. | | `verifyTwilioSignature` | `value` | Public export. | ### `@fabric-harness/channels/whatsapp` | Export | Kind | Summary | | --- | --- | --- | | `createWhatsAppChannel` | `value` | Public export. | | `parseWhatsAppConversationKey` | `value` | Public export. | | `replyInWhatsApp` | `value` | Public export. | | `WhatsAppChannel` | `type` | Public export. | | `WhatsAppChannelOptions` | `type` | Public export. | | `whatsAppConversationKey` | `value` | Public export. | | `WhatsAppConversationRef` | `type` | Public export. | | `WhatsAppWebhookPayload` | `type` | Public export. | ### `@fabric-harness/channels/google-chat` | Export | Kind | Summary | | --- | --- | --- | | `createGoogleChatChannel` | `value` | Public export. | | `GoogleChatChannel` | `type` | Public export. | | `GoogleChatChannelOptions` | `type` | Public export. | | `googleChatConversationKey` | `value` | Public export. | | `GoogleChatConversationRef` | `type` | Public export. | | `GoogleChatEvent` | `type` | Public export. | | `parseGoogleChatConversationKey` | `value` | Public export. | | `replyInGoogleChat` | `value` | Public export. | | `verifyGoogleChatRequest` | `value` | Public export. | ### `@fabric-harness/channels/linear` | Export | Kind | Summary | | --- | --- | --- | | `commentOnLinearIssue` | `value` | Public export. | | `createLinearChannel` | `value` | Public export. | | `LinearChannel` | `type` | Public export. | | `LinearChannelOptions` | `type` | Public export. | | `linearConversationKey` | `value` | Public export. | | `LinearConversationRef` | `type` | Public export. | | `LinearWebhookPayload` | `type` | Public export. | | `parseLinearConversationKey` | `value` | Public export. | ### `@fabric-harness/channels/notion` | Export | Kind | Summary | | --- | --- | --- | | `commentOnNotionPage` | `value` | Public export. | | `createNotionChannel` | `value` | Public export. | | `NotionChannel` | `type` | Public export. | | `NotionChannelOptions` | `type` | Public export. | | `notionConversationKey` | `value` | Public export. | | `NotionConversationRef` | `type` | Public export. | | `NotionWebhookPayload` | `type` | Public export. | | `parseNotionConversationKey` | `value` | Public export. | ### `@fabric-harness/channels/stripe` | Export | Kind | Summary | | --- | --- | --- | | `createStripeChannel` | `value` | Public export. | | `parseStripeConversationKey` | `value` | Public export. | | `StripeChannel` | `type` | Public export. | | `StripeChannelOptions` | `type` | Public export. | | `stripeConversationKey` | `value` | Public export. | | `StripeConversationRef` | `type` | Public export. | | `StripeEventPayload` | `type` | Public export. | | `updateStripeCustomer` | `value` | Public export. | | `verifyStripeSignature` | `value` | Public export. | ### `@fabric-harness/channels/zendesk` | Export | Kind | Summary | | --- | --- | --- | | `commentOnZendeskTicket` | `value` | Public export. | | `createZendeskChannel` | `value` | Public export. | | `parseZendeskConversationKey` | `value` | Public export. | | `ZendeskChannel` | `type` | Public export. | | `ZendeskChannelOptions` | `type` | Public export. | | `zendeskConversationKey` | `value` | Public export. | | `ZendeskConversationRef` | `type` | Public export. | | `ZendeskWebhookPayload` | `type` | Public export. | ### `@fabric-harness/channels/intercom` | Export | Kind | Summary | | --- | --- | --- | | `createIntercomChannel` | `value` | Public export. | | `IntercomChannel` | `type` | Public export. | | `IntercomChannelOptions` | `type` | Public export. | | `intercomConversationKey` | `value` | Public export. | | `IntercomConversationRef` | `type` | Public export. | | `IntercomWebhookPayload` | `type` | Public export. | | `parseIntercomConversationKey` | `value` | Public export. | | `replyInIntercom` | `value` | Public export. | ### `@fabric-harness/channels/shopify` | Export | Kind | Summary | | --- | --- | --- | | `createShopifyChannel` | `value` | Public export. | | `parseShopifyConversationKey` | `value` | Public export. | | `ShopifyChannel` | `type` | Public export. | | `ShopifyChannelOptions` | `type` | Public export. | | `shopifyConversationKey` | `value` | Public export. | | `ShopifyConversationRef` | `type` | Public export. | | `ShopifyWebhookPayload` | `type` | Public export. | | `updateShopifyOrderNote` | `value` | Public export. | ### `@fabric-harness/channels/messenger` | Export | Kind | Summary | | --- | --- | --- | | `createMessengerChannel` | `value` | Public export. | | `MessengerChannel` | `type` | Public export. | | `MessengerChannelOptions` | `type` | Public export. | | `messengerConversationKey` | `value` | Public export. | | `MessengerConversationRef` | `type` | Public export. | | `MessengerWebhookPayload` | `type` | Public export. | | `parseMessengerConversationKey` | `value` | Public export. | | `replyInMessenger` | `value` | Public export. | ### `@fabric-harness/channels/resend` | Export | Kind | Summary | | --- | --- | --- | | `createResendChannel` | `value` | Public export. | | `parseResendConversationKey` | `value` | Public export. | | `ResendChannel` | `type` | Public export. | | `ResendChannelOptions` | `type` | Public export. | | `resendConversationKey` | `value` | Public export. | | `ResendConversationRef` | `type` | Public export. | | `ResendWebhookPayload` | `type` | Public export. | | `sendWithResend` | `value` | Public export. | | `verifyResendSignature` | `value` | Public export. | ### `@fabric-harness/channels/salesforce-marketing-cloud` | Export | Kind | Summary | | --- | --- | --- | | `createSalesforceMarketingCloudChannel` | `value` | Public export. | | `parseSalesforceMarketingCloudConversationKey` | `value` | Public export. | | `SalesforceMarketingCloudChannel` | `type` | Public export. | | `SalesforceMarketingCloudChannelOptions` | `type` | Public export. | | `salesforceMarketingCloudConversationKey` | `value` | Public export. | | `SalesforceMarketingCloudConversationRef` | `type` | Public export. | | `SalesforceMarketingCloudEvent` | `type` | Public export. | | `SalesforceMarketingCloudWebhookPayload` | `type` | Public export. | | `sendMarketingCloudMessage` | `value` | Public export. | ## @fabric-harness/cli ### `@fabric-harness/cli` | Export | Kind | Summary | | --- | --- | --- | | _No named exports_ | | | ### `@fabric-harness/cli/config` | Export | Kind | Summary | | --- | --- | --- | | `defineConfig` | `value` | Public export. | | `DefineConfigInput` | `type` | Public export. | ## @fabric-harness/cloudflare ### `@fabric-harness/cloudflare` | Export | Kind | Summary | | --- | --- | --- | | `cloudflare` | `value` | One-call wiring for a Cloudflare-native agent: model provider, Workers AI binding tools, a safe default egress policy, and an optional Durable Object session store. | | `CLOUDFLARE_BUILD_TARGET` | `value` | Public export. | | `cloudflareAgent` | `value` | Public export. | | `CloudflareAgentOptions` | `type` | Public export. | | `CloudflareBundle` | `type` | Public export. | | `CloudflareBundleConfig` | `type` | Public export. | | `CloudflareCronHandlerOptions` | `type` | Public export. | | `CloudflareDurableObjectSessionStoreOptions` | `type` | Public export. | | `CloudflareDurableObjectSqlStorage` | `type` | Public export. | | `CloudflareDurablePersistenceStores` | `type` | Public export. | | `CloudflareExecutionContextLike` | `type` | Public export. | | `CloudflareExtension` | `type` | Public export. | | `CloudflareR2BucketLike` | `type` | Public export. | | `CloudflareR2ListResultLike` | `type` | Public export. | | `CloudflareR2ObjectBodyLike` | `type` | Public export. | | `CloudflareSandboxEnvOptions` | `type` | Public export. | | `CloudflareSandboxExecResult` | `type` | Public export. | | `CloudflareSandboxFileInfo` | `type` | Public export. | | `CloudflareSandboxLike` | `type` | Public export. | | `CloudflareSandboxProcessLike` | `type` | Public export. | | `CloudflareScheduledController` | `type` | Public export. | | `CloudflareScheduledJob` | `type` | Public export. | | `cloudflareScheduleIdempotencyKey` | `value` | Public export. | | `createCloudflareCronHandler` | `value` | Build a Cloudflare `scheduled()` handler that admits matching jobs through the finite-run seam. | | `createCloudflareDurableObjectSessionStore` | `value` | Minimal Durable Object SQL-backed SessionStore used by the Cloudflare build target. It intentionally depends only on the small SQL shape exposed by Workers Durable Objects so Cloudflare SDK types do not leak into the SDK. | | `createCloudflareDurablePersistenceStores` | `value` | Durable Object SQLite stores for the v2 submission and stream lifecycle. | | `createCloudflareRunStore` | `value` | Cloudflare Durable Object SQL-backed RunStore for workflow events. Enforces append-only semantics via a `UNIQUE(run_id, event_index)` primary key on the `fabric_harness_workflow_events` table. Duplicate event indexes for the same runId are rejected at the storage layer. A `BEFORE INSERT` trigger on `fabric_harness_runs` deletes existing events when a run is reset with the same id (same-ID reset behaviour). | | `createCloudflareSandboxEnv` | `value` | Adapt an instance returned by `getSandbox(env.Sandbox, id)` from `@cloudflare/sandbox` into Fabric's provider-neutral SandboxEnv contract. | | `defineCloudflareAgent` | `value` | Public export. | | `DefineCloudflareAgentOptions` | `type` | Public export. | | `extend` | `value` | Public export. | | `ExtensionClass` | `type` | Public export. | | `MockCloudflareModelProvider` | `value` | A deterministic `ModelProvider` for Cloudflare agent tests and init templates. Returns structured responses without requiring real Cloudflare credentials. | | `MockCloudflareModelProviderOptions` | `type` | Public export. | | `r2FilesystemSource` | `value` | Read Cloudflare R2 objects as a Fabric filesystem source. Mount it with `withFilesystemSources()` so support agents can grep/read R2-backed knowledge bases through the normal sandbox tools. | | `R2FilesystemSourceOptions` | `type` | Public export. | | `registerCloudflareSandboxRefDecoder` | `value` | Register cross-process/Worker attachment for Cloudflare Sandbox IDs. | | `resolveCloudflareExtension` | `value` | Public export. | | `ResolvedCloudflareExtension` | `type` | Public export. | | `resolveToolRefs` | `value` | Public export. | ### `@fabric-harness/cloudflare/agent` | Export | Kind | Summary | | --- | --- | --- | | `cloudflareAgent` | `value` | Public export. | | `CloudflareAgentOptions` | `type` | Public export. | | `defineCloudflareAgent` | `value` | Public export. | | `DefineCloudflareAgentOptions` | `type` | Public export. | | `resolveToolRefs` | `value` | Public export. | ### `@fabric-harness/cloudflare/workers-ai` | Export | Kind | Summary | | --- | --- | --- | | `BUILTIN_WORKERS_AI_MODEL_INFO` | `value` | Public export. | | `CLOUDFLARE_WORKERS_AI_MODEL_PREFIX` | `value` | Reserved model-string prefix that routes through the Workers AI binding provider. `init({ model: 'cloudflare/@cf/meta/llama-3.1-8b-instruct' })` picks this provider when one is registered with the matching name. | | `CloudflareWorkersAIBindingLike` | `type` | Structural shape of Cloudflare Workers' AI binding (`env.AI`). The real `Ai` type lives in `@cloudflare/workers-types`; we accept a narrower structural type so this package doesn't peer-dep `workers-types`. The `run()` method accepts the OpenAI Chat Completions request body and returns an OpenAI-shaped response when `stream: false` is passed. | | `CloudflareWorkersAIGatewayOptions` | `type` | Public export. | | `CloudflareWorkersAIModelInfo` | `type` | Public export. | | `CloudflareWorkersAIModelProvider` | `value` | Model provider that dispatches to Cloudflare Workers AI via the platform binding (`env.AI.run()`) instead of HTTP. Use on a Cloudflare Workers deployment to skip API tokens, gain Workers-AI-native rate limiting, and keep inference inside Cloudflare's network. Workers AI accepts the OpenAI Chat Completions request body verbatim, so we serialize through the SDK's `toOpenAIMessage`/`toOpenAITool` helpers and parse the binding's response with `openAIChatCompletionToModelResponse`. See the package declarations for an example. Pair with the `cloudflare/` model prefix to make routing explicit when... | | `CloudflareWorkersAIModelProviderOptions` | `type` | Public export. | | `mapReasoningEffort` | `value` | Map Fabric's ordinal `ThinkingLevel` to Cloudflare's shared `reasoning_effort` option: `minimal`/`low` → `low`, `medium` → `medium`, `high`/`xhigh` → `high`. `'off'` is handled by the caller (the field is omitted) and never reaches this function. | | `stripCloudflarePrefix` | `value` | Strip the `cloudflare/` routing prefix from a model id, leaving the raw Workers AI model id (e.g. `@cf/meta/llama-3.1-8b-instruct`). | | `WorkersAIReasoningEffort` | `type` | Cloudflare Workers AI reasoning-effort wire values. | ### `@fabric-harness/cloudflare/shell` | Export | Kind | Summary | | --- | --- | --- | | `CloudflareShellCodeExecutorLike` | `type` | Public export. | | `CloudflareShellCodeInput` | `type` | Public export. | | `CloudflareShellCodeToolOptions` | `type` | Public export. | | `CloudflareShellContext` | `type` | Public export. | | `CloudflareShellWorkspaceSandbox` | `type` | Public export. | | `CloudflareShellWorkspaceSandboxOptions` | `type` | Public export. | | `CloudflareWorkerLoaderLike` | `type` | Public export. | | `createCloudflareShellCodeTool` | `value` | Public export. | | `createCloudflareShellCodeToolFromExecutor` | `value` | Public export. | | `createCloudflareShellWorkspaceSandboxEnv` | `value` | Public export. | | `getCloudflareShellContext` | `value` | Public export. | | `getCloudflareShellWorkspaceSandbox` | `value` | Public export. | | `getDefaultCloudflareWorkspace` | `value` | Construct the default | | `GetDefaultCloudflareWorkspaceOptions` | `type` | Public export. | | `hydrateCloudflareWorkspaceFromR2` | `value` | Eagerly copy R2 objects into a | | `HydrateCloudflareWorkspaceFromR2Options` | `type` | Public export. | | `registerCloudflareShellWorkspaceRefDecoder` | `value` | Register attachment for a durable Cloudflare Shell workspace routing ID. | | `runWithCloudflareShellContext` | `value` | Public export. | ### `@fabric-harness/cloudflare/scheduled` | Export | Kind | Summary | | --- | --- | --- | | `CloudflareCronHandlerOptions` | `type` | Public export. | | `CloudflareExecutionContextLike` | `type` | Public export. | | `CloudflareScheduledController` | `type` | Public export. | | `CloudflareScheduledJob` | `type` | Public export. | | `cloudflareScheduleIdempotencyKey` | `value` | Public export. | | `createCloudflareCronHandler` | `value` | Build a Cloudflare `scheduled()` handler that admits matching jobs through the finite-run seam. | ### `@fabric-harness/cloudflare/persistence` | Export | Kind | Summary | | --- | --- | --- | | `CloudflareDurablePersistenceStores` | `type` | Public export. | | `createCloudflareDurablePersistenceStores` | `value` | Durable Object SQLite stores for the v2 submission and stream lifecycle. | ## @fabric-harness/connectors ### `@fabric-harness/connectors` | Export | Kind | Summary | | --- | --- | --- | | `assertSandboxCertification` | `value` | Public export. | | `AzureBlobBodyLike` | `type` | Public export. | | `AzureBlobFilesystemClientLike` | `type` | Public export. | | `azureBlobFilesystemSource` | `value` | Public export. | | `AzureBlobListResultLike` | `type` | Public export. | | `certifySandboxAdapter` | `value` | Exercise the portable Fabric sandbox contract and return secret-free, machine-readable evidence suitable for a CI artifact. | | `daytonaSandbox` | `value` | Public export. | | `daytonaSandboxFactory` | `value` | Public export. | | `DaytonaSandboxLike` | `type` | Public export. | | `DaytonaSandboxOptions` | `type` | Public export. | | `e2bSandbox` | `value` | Public export. | | `E2BSandboxLike` | `type` | Public export. | | `FilesystemSource` | `type` | A read-only content source that can be mounted into a sandbox at sandbox-creation time. The agent then has built-in `read`, `glob`, and `grep` tools available over the mounted content — no retrieval pipeline, no embeddings, no vector store required. Sources are intentionally minimal: they yield (path, content) pairs. Implementations decide how to enumerate (eager vs lazy is up to the source author) — the mount step pulls the full set into the sandbox. | | `ModalFileInfoLike` | `type` | Public export. | | `ModalFilesystemLike` | `type` | Public export. | | `ModalProcessLike` | `type` | Public export. | | `ModalReadStreamLike` | `type` | Public export. | | `modalSandbox` | `value` | Public export. | | `ModalSandboxLike` | `type` | Public export. | | `modalSdkSandbox` | `value` | Adapt a native Modal TypeScript SDK `Sandbox` without exposing credentials to the Fabric runtime or model context. | | `ModalSdkSandboxLike` | `type` | Structural subset implemented by `modal` 0.9 `Sandbox`. | | `ModalSdkSandboxOptions` | `type` | Public export. | | `ObjectStorageFilesystemSourceOptions` | `type` | Public export. | | `ProviderCleanupOptions` | `type` | Public export. | | `RemoteAdapterOptions` | `type` | Public export. | | `remoteSandbox` | `value` | Dependency-free structural connector for provider-owned remote sandboxes. Provider-specific packages can keep their SDK/client types private and map them to RemoteSandboxApi, then return this SandboxFactory. Credentials stay in the provider client and are never serialized into Fabric session history. | | `RemoteSandboxApi` | `type` | Public export. | | `RemoteSandboxConnectorOptions` | `type` | Public export. | | `remoteSandboxEnv` | `value` | Public export. | | `RemoteSandboxOptions` | `type` | Public export. | | `S3FilesystemClientLike` | `type` | Public export. | | `s3FilesystemSource` | `value` | Public export. | | `S3ObjectBodyLike` | `type` | Public export. | | `S3ObjectListResultLike` | `type` | Public export. | | `SANDBOX_PROVIDER_COMPATIBILITY` | `value` | Public export. | | `SandboxAdapterValidationOptions` | `type` | Public export. | | `SandboxAdapterValidationResult` | `type` | Public export. | | `SandboxCertificationCheck` | `type` | Public export. | | `SandboxCertificationCheckName` | `type` | Public export. | | `SandboxCertificationError` | `value` | Public export. | | `SandboxCertificationOptions` | `type` | Public export. | | `SandboxCertificationReport` | `type` | Public export. | | `SandboxFactory` | `type` | Public export. | | `validateSandboxAdapter` | `value` | Public export. | ### `@fabric-harness/connectors/s3` | Export | Kind | Summary | | --- | --- | --- | | `s3Source` | `value` | Read-only S3 connector. Mount with `session.mount(mountAt, s3Source(...))`. The agent's built-in `read`, `grep`, `glob`, and `bash` tools then operate over the mounted prefix as if it were a local directory. | | `S3SourceOptions` | `type` | Concrete S3 connector backed by `@aws-sdk/client-s3`. Users pass bucket config; this module constructs the SDK client internally and adapts it to Fabric Harness's `FilesystemSource` contract. Install peer dep: ```sh npm install | | `s3Writer` | `value` | Public export. | | `S3Writer` | `type` | Public export. | | `S3WriterOptions` | `type` | Write-side helper for S3. Use to publish artifacts or mount a writable view. See the package declarations for an example. | ### `@fabric-harness/connectors/azure-blob` | Export | Kind | Summary | | --- | --- | --- | | `azureBlobSource` | `value` | Public export. | | `AzureBlobSourceOptions` | `type` | Concrete Azure Blob connector backed by `@azure/storage-blob`. Install peer deps: ```sh npm install | | `azureBlobWriter` | `value` | Public export. | | `AzureBlobWriter` | `type` | Public export. | | `AzureBlobWriterOptions` | `type` | Public export. | ### `@fabric-harness/connectors/gcs` | Export | Kind | Summary | | --- | --- | --- | | `gcsSource` | `value` | Public export. | | `GcsSourceOptions` | `type` | Concrete Google Cloud Storage connector backed by `@google-cloud/storage`. Install peer dep: ```sh npm install | | `gcsWriter` | `value` | Public export. | | `GcsWriter` | `type` | Public export. | | `GcsWriterOptions` | `type` | Public export. | ### `@fabric-harness/connectors/github` | Export | Kind | Summary | | --- | --- | --- | | `githubSource` | `value` | Public export. | | `GithubSourceOptions` | `type` | Read-only GitHub repository connector backed by `octokit`. Mounts a repository (at a given ref) as files inside the sandbox. Uses the Git Trees API in recursive mode for efficient enumeration. Install peer dep: See the package declarations for an example. | ### `@fabric-harness/connectors/databricks-volume` | Export | Kind | Summary | | --- | --- | --- | | `databricksVolumeSource` | `value` | Public export. | | `DatabricksVolumeSourceOptions` | `type` | Databricks Unity Catalog Volume connector backed by the Files API (`/api/2.0/fs/files/...`). No SDK dependency — uses `fetch` directly. Volume paths are addressed as `/Volumes/<catalog>/<schema>/<volume>/<path>`. Usage: See the package declarations for an example. | | `databricksVolumeWriter` | `value` | Public export. | | `DatabricksVolumeWriter` | `type` | Public export. | | `DatabricksVolumeWriterOptions` | `type` | Public export. | ### `@fabric-harness/connectors/k8s` | Export | Kind | Summary | | --- | --- | --- | | `createKubernetesEgressNetworkPolicy` | `value` | Build a deny-by-default Kubernetes egress boundary. Domain filtering belongs in the selected proxy; Kubernetes NetworkPolicy is intentionally limited to DNS, that proxy, and explicit private endpoint CIDRs. | | `createKubernetesPod` | `value` | Public export. | | `createKubernetesPodFromImage` | `value` | Public export. | | `CreateKubernetesPodFromImageOptions` | `type` | Provision an ephemeral pod from an image and return a `KubernetesPodLike` ready for `kubernetesSandbox()`. Works against any cluster reachable via the resolved kubeconfig — in-cluster, `$KUBECONFIG`, or `~/.kube/config`. The pod is created with `restartPolicy: Never` and runs `sleep infinity` so it stays attachable. `cleanup()` (when wired via `kubernetesSandbox`'s `cleanup: true`) deletes the pod. For Azure-managed clusters, prefer `aksSandbox()` from `@fabric-harness/azure/aks-sandbox` which adds ARM-resolved kubeconfig. See the package declarations for an example. | | `CreateKubernetesPodOptions` | `type` | Convenience helper: build a `KubernetesPodLike` from a `@kubernetes/client-node` `KubeConfig` plus pod identity. Suitable when `kubectl` already gets you in the door — for AKS, prefer `aksSandbox()` from `@fabric-harness/azure`. Files are transferred over `exec` using base64-encoded `cat` / `tee`. Works for text and small binaries; for large objects mount external storage via `s3Source` / `gcsSource` / `azureBlobSource` instead. | | `KubernetesEgressNetworkPolicyOptions` | `type` | Public export. | | `KubernetesNetworkPolicyManifest` | `type` | Public export. | | `KubernetesPodLike` | `type` | Structural Kubernetes pod adapter. The caller wires up `@kubernetes/client-node` (or any SPDY/exec-capable client) and supplies an object that satisfies `KubernetesPodLike`. Why structural: cluster auth, RBAC, and the exec wire format vary too much across environments (in-cluster, kubeconfig, AKS managed identity, GKE Workload Identity, EKS IRSA, …) for a single concrete adapter to be sensible. Pattern matches `daytonaSandbox` / `e2bSandbox`. For Azure-hosted clusters use the higher-level `aksSandbox()` from `@fabric-harness/azure`, which wires this adapter to Azure-managed credentials. | | `kubernetesSandbox` | `value` | Public export. | | `KubernetesSandboxOptions` | `type` | Public export. | ### `@fabric-harness/connectors/vercel` | Export | Kind | Summary | | --- | --- | --- | | `vercelSandbox` | `value` | Wrap a Vercel Sandbox instance as a Fabric Harness `SandboxEnv`. The sandbox's filesystem and shell exec are mapped to the fabric contract; `cleanup: true` calls `sandbox.stop()` when the Fabric session tears down. See the package declarations for an example. Vercel's `runCommand` accepts cwd, env, AbortSignal, and output streams. Fabric maps its millisecond timeout to an AbortSignal and resolves the command object's async `stdout()`/`stderr()` methods used by SDK 1.x. | | `vercelSandboxFactory` | `value` | Convenience factory that returns a `SandboxFactory`, suitable for `init({ sandbox: vercelSandboxFactory(...) })`. Provisions a fresh sandbox on first use via the supplied `create()` callback. | | `VercelSandboxLike` | `type` | Structural shape of `@vercel/sandbox`'s `Sandbox` instance, kept narrow so this package doesn't peer-dep on `@vercel/sandbox` directly. Pass any object satisfying this contract — the real `Sandbox` from `@vercel/sandbox` does. See https://vercel.com/docs/vercel-sandbox/sdk-reference for the upstream API. This adapter targets the post-GA shape (`runCommand`, `fs.*`). | | `VercelSandboxOptions` | `type` | Public export. | ### `@fabric-harness/connectors/modal` | Export | Kind | Summary | | --- | --- | --- | | `ModalFileInfoLike` | `type` | Public export. | | `ModalFilesystemLike` | `type` | Public export. | | `ModalProcessLike` | `type` | Public export. | | `ModalReadStreamLike` | `type` | Public export. | | `modalSdkSandbox` | `value` | Adapt a native Modal TypeScript SDK `Sandbox` without exposing credentials to the Fabric runtime or model context. | | `ModalSdkSandboxLike` | `type` | Structural subset implemented by `modal` 0.9 `Sandbox`. | | `ModalSdkSandboxOptions` | `type` | Public export. | ### `@fabric-harness/connectors/sandbox-refs` | Export | Kind | Summary | | --- | --- | --- | | `RegisterStandardDecodersOptions` | `type` | Standard cross-process sandbox-ref decoders for the connectors that ship here. Call this once at startup in any process that needs to attach to sandboxes encoded by `session.sandboxRef({ portable: true })`. Each `connect*` callback is responsible for hydrating a provider-native client object from `providerData`; the connectors package wraps that client in a `SandboxEnv` exactly the way the upstream factory functions do (`daytonaSandbox`, `e2bSandbox`, `modalSandbox`, `kubernetesSandbox`). See the package declarations for an example. | | `registerStandardSandboxRefDecoders` | `value` | Public export. | ### `@fabric-harness/connectors/sandbox-certification` | Export | Kind | Summary | | --- | --- | --- | | `assertSandboxCertification` | `value` | Public export. | | `certifySandboxAdapter` | `value` | Exercise the portable Fabric sandbox contract and return secret-free, machine-readable evidence suitable for a CI artifact. | | `SandboxCertificationCheck` | `type` | Public export. | | `SandboxCertificationCheckName` | `type` | Public export. | | `SandboxCertificationError` | `value` | Public export. | | `SandboxCertificationOptions` | `type` | Public export. | | `SandboxCertificationReport` | `type` | Public export. | ## @fabric-harness/databases ### `@fabric-harness/databases` | Export | Kind | Summary | | --- | --- | --- | | `assertReadOnlyStatement` | `value` | Public export. | | `governedDatabaseTool` | `value` | Common database tool boundary: effect metadata, timeout, size cap, and redacted failures. | | `GovernedDatabaseToolOptions` | `type` | Public export. | | `MongoCollectionLike` | `type` | Public export. | | `MongoCursorLike` | `type` | Public export. | | `mongoFindTool` | `value` | Collection-bound find tool. Host code constructs the filter and projection. | | `MongoFindToolOptions` | `type` | Public export. | | `MysqlClientLike` | `type` | Public export. | | `mysqlTool` | `value` | Public export. | | `MysqlToolOptions` | `type` | Public export. | | `PostgresClientLike` | `type` | Public export. | | `postgresTool` | `value` | Fixed-statement Postgres tool. The model supplies values, never SQL text. | | `PostgresToolOptions` | `type` | Public export. | | `RedisClientLike` | `type` | Public export. | | `RedisToolOptions` | `type` | Public export. | | `redisTools` | `value` | Public export. | | `SqliteClientLike` | `type` | Public export. | | `SqliteStatementLike` | `type` | Public export. | | `sqliteTool` | `value` | Public export. | | `SqliteToolOptions` | `type` | Public export. | ### `@fabric-harness/databases/postgres` | Export | Kind | Summary | | --- | --- | --- | | `PostgresClientLike` | `type` | Public export. | | `postgresTool` | `value` | Fixed-statement Postgres tool. The model supplies values, never SQL text. | | `PostgresToolOptions` | `type` | Public export. | ### `@fabric-harness/databases/mysql` | Export | Kind | Summary | | --- | --- | --- | | `MysqlClientLike` | `type` | Public export. | | `mysqlTool` | `value` | Public export. | | `MysqlToolOptions` | `type` | Public export. | ### `@fabric-harness/databases/sqlite` | Export | Kind | Summary | | --- | --- | --- | | `SqliteClientLike` | `type` | Public export. | | `SqliteStatementLike` | `type` | Public export. | | `sqliteTool` | `value` | Public export. | | `SqliteToolOptions` | `type` | Public export. | ### `@fabric-harness/databases/mongodb` | Export | Kind | Summary | | --- | --- | --- | | `MongoCollectionLike` | `type` | Public export. | | `MongoCursorLike` | `type` | Public export. | | `mongoFindTool` | `value` | Collection-bound find tool. Host code constructs the filter and projection. | | `MongoFindToolOptions` | `type` | Public export. | ### `@fabric-harness/databases/redis` | Export | Kind | Summary | | --- | --- | --- | | `RedisClientLike` | `type` | Public export. | | `RedisToolOptions` | `type` | Public export. | | `redisTools` | `value` | Public export. | ## @fabric-harness/databricks ### `@fabric-harness/databricks` | Export | Kind | Summary | | --- | --- | --- | | `AgentEvaluationRecord` | `type` | Public export. | | `appServicePrincipalFromEnv` | `value` | The service principal a Databricks App runs as, from the standard app runtime environment (`DATABRICKS_HOST` / `DATABRICKS_CLIENT_ID` / `DATABRICKS_CLIENT_SECRET`). Returns `undefined` when any variable is missing, so callers can fall back to explicit configuration. | | `buildMlflowTrace` | `value` | Build the MLflow V3 `POST /api/3.0/mlflow/traces` payload for one settled submission. Pure and deterministic: trace/span ids derive from the submission id (+ span index) via SHA-256, so replaying the same settled submission produces byte-identical ids. The root span covers the whole submission; buffered `TelemetrySpan`s become its children. | | `ConsumptionGroupBy` | `type` | Public export. | | `ConsumptionSummary` | `type` | Public export. | | `ConsumptionSummaryOptions` | `type` | Public export. | | `createDatabricksClient` | `value` | Public export. | | `createDatabricksRagChain` | `value` | Thin orchestration of Databricks-native Vector Search + Model Serving for the online RAG inference chain documented in the Databricks AI Cookbook. Prefer `databricksRagChain` from the package root when you have a `databricks({ vectorSearch })` config — it wires the bundle for you. Does **not** implement offline chunking/indexing (use Databricks Jobs/Lakeflow). Does **not** replace Mosaic Agent Evaluation — export turns with rag-eval helpers. | | `databricks` | `value` | One-call wiring for a Databricks-native agent: model serving, a UC principal threaded through model + data + state, governed tools, an approval/egress policy, and (optionally) Lakebase-backed durable state. Also registers `databricks/*` model refs. Everything is layered on Unity Catalog — never a replacement for it. | | `DATABRICKS_API_VERSIONS` | `value` | Public export. | | `DATABRICKS_AUTH_MODES` | `value` | Public export. | | `DATABRICKS_PACKAGE_COMPATIBILITY` | `value` | Public export. | | `databricksActualCostSource` | `value` | Create a Databricks-backed `ActualCostSource` that queries `system.billing.usage` joined to `system.billing.list_prices` for real spend data. Results are cached per-scope-key for `cacheTtlMs` (default 60s) to avoid hammering the SQL warehouse. | | `DatabricksActualCostSourceOptions` | `type` | Public export. | | `databricksAgent` | `value` | Public export. | | `DatabricksAgentOptions` | `type` | Public export. | | `databricksAiQueryTool` | `value` | `ai_query(endpoint, request)` — run model inference inside the SQL warehouse against a serving endpoint. Drives both serving and SQL-warehouse consumption. Uses named SQL parameters, so the endpoint and request are never string-interpolated into SQL. | | `DatabricksAiQueryToolOptions` | `type` | Public export. | | `databricksApp` | `value` | Public export. | | `DatabricksAppOptions` | `type` | Production preset for agents hosted on Databricks Apps (v2, workstream C5). One call resolves the runtime from the standard app environment: the app's service principal (client-credentials OAuth), the workspace REST client and serving-endpoint model provider, Lakebase-backed persistence for sessions + submissions + conversation streams, and submission-keyed telemetry — all running as one governed identity that Unity Catalog enforces. See the package declarations for an example. | | `DatabricksApprovalRuleOptions` | `type` | Public export. | | `databricksApprovalRules` | `value` | Builds approval rules (one per gated tool) for `CapabilityPolicy.toolPolicy.approvalRules`. | | `DatabricksAppRuntime` | `type` | Public export. | | `DatabricksAppUserAuthorizationInspection` | `type` | Public export. | | `DatabricksBundle` | `type` | Public export. | | `DatabricksBundleConfig` | `type` | Public export. | | `DatabricksCertificationCheck` | `type` | Public export. | | `DatabricksCertificationEvidence` | `type` | Public export. | | `DatabricksCertificationResult` | `type` | Public export. | | `DatabricksCertificationStatus` | `type` | Public export. | | `DatabricksClientOptions` | `type` | Public export. | | `DatabricksCloud` | `type` | Public export. | | `databricksCompatibilityRecord` | `value` | Convert successful, fully identified certification evidence into a publishable matrix row. | | `databricksConsumption` | `value` | System-Tables consumption reporting. Reads `system.billing.usage` joined to `list_prices` to report real DBUs + list cost — the "prove the consumption" artifact for chargeback and partner credit. | | `DatabricksConsumption` | `type` | Public export. | | `DatabricksConsumptionOptions` | `type` | Public export. | | `databricksConsumptionTool` | `value` | Agent-callable consumption report over the last `days` (default 30) or an explicit date window. | | `DatabricksConsumptionToolOptions` | `type` | Public export. | | `DatabricksCostReconciliationOptions` | `type` | Public export. | | `DatabricksCostReconciliationResult` | `type` | Public export. | | `databricksEmbeddings` | `value` | Embeddings via a Mosaic AI serving **embedding** endpoint. Databricks exposes the OpenAI-compatible embeddings surface at `<host>/serving-endpoints/embeddings` with the endpoint name as `model`. Reuses the bundle's REST client, so it inherits the rotating UC-principal token. (URL/response schema worth a smoke test against a live embedding endpoint — chat and embeddings share the OpenAI-compatible convention but endpoint families can differ.) | | `DatabricksEmbeddingsOptions` | `type` | Public export. | | `databricksFeatureLookupTool` | `value` | Low-latency feature lookup from a Databricks **Feature Serving** endpoint (backed by an Online Table). The model passes primary-key records; the endpoint returns the served feature values. Drives serving consumption and keeps features governed by Unity Catalog under the bundle principal. | | `DatabricksFeatureLookupToolOptions` | `type` | Public export. | | `databricksFoundationModelProvider` | `value` | Databricks inference provider supporting both Unity AI Gateway model services and custom Model Serving endpoints. In `auto` mode, fully-qualified `system.ai.*` models use AI Gateway and other endpoint names retain the `/serving-endpoints` route. | | `databricksGenieTool` | `value` | AI/BI Genie — delegate a natural-language analytics question to a governed Genie space. Genie generates and runs SQL on the warehouse and returns an answer; this tool returns the answer text, the generated SQL, and the result rows. Pass `conversationId` to continue a multi-turn thread. Genie response field names vary by API version — parsed defensively; smoke-test against a live space. | | `DatabricksGenieToolOptions` | `type` | Public export. | | `DatabricksGovernanceOptions` | `type` | Public export. | | `databricksGovernancePolicy` | `value` | Assembles a `CapabilityPolicy` for a Databricks tool set: approval routing for sensitive operations (if `stewardAudience` is set) plus an egress allowlist pinned to the workspace host(s). Used by the `databricks()` bundle; can also be merged into an agent's own policy. | | `DatabricksGovernancePolicyOptions` | `type` | Public export. | | `databricksIdentity` | `value` | Builds a cached, auto-refreshing token provider for a Databricks principal. | | `DatabricksInferenceMode` | `type` | Public export. | | `databricksJobs` | `value` | Public export. | | `DatabricksJobs` | `value` | Public export. | | `DatabricksJobsWaitOptions` | `type` | Public export. | | `databricksLakeflowTools` | `value` | The default Lakeflow tool set added by the bundle's `lakeflow` option. | | `DatabricksLineageEvidenceRow` | `type` | Public export. | | `DatabricksLineageRecord` | `type` | Governance for Databricks tools is **layered on top of Unity Catalog, never a replacement.** UC enforces table/row/column ACLs natively via the acting principal (see identity.ts). These helpers add the things UC doesn't: an **audit/lineage** stamp on every tool call, **approval routing** for sensitive operations, and an **egress allowlist** pinned to the workspace — all expressed through the SDK's existing `CapabilityPolicy`, so the loop enforces them without a bespoke engine. | | `databricksMlflowLogMetricTool` | `value` | Public export. | | `databricksMlflowLogParamTool` | `value` | Public export. | | `databricksModelBaseUrl` | `value` | Build the OpenAI-compatible base URL used for one Databricks inference mode. | | `DatabricksModelOptions` | `type` | Public export. | | `DatabricksModelService` | `type` | Public export. | | `DatabricksModelServiceDiscoveryOptions` | `type` | Public export. | | `databricksNotebookTool` | `value` | Public export. | | `databricksPersistence` | `value` | One-call Lakebase persistence for Databricks-hosted agents. See the package declarations for an example. The Postgres store implementations come from `@fabric-harness/node` (optional peer) and run unchanged against Lakebase — the only Databricks specifics are database-credential exchange, refresh caching, and TLS defaults from `lakebaseClient`. | | `DatabricksPersistence` | `type` | Everything the v2 runtime persists, on Lakebase, in one adapter: sessions (canonical conversation state), durable agent submissions (admission/FIFO/leases/settlement), and offset-addressable conversation streams. Implements the SDK `PersistenceAdapter` contract, so it plugs straight into `init({ persistence })`; the extra `connect*` methods feed `startDevServer({ submissionStore, conversationStreamStore })`. | | `DatabricksPersistenceOptions` | `type` | Public export. | | `DatabricksPersistenceStoreModule` | `type` | The slice of `@fabric-harness/node` this adapter assembles. Loaded dynamically (node is an optional peer — Databricks Apps always run on Node, but this package stays importable without it); injectable for tests. | | `databricksPipelineListTool` | `value` | Lakeflow Declarative Pipelines (formerly DLT). These drive heavy continuous-compute consumption. `start`/`stop` mutate pipeline state (`effect: 'execute'`, governable); `list`/`status` are reads. | | `databricksPipelineStartTool` | `value` | Public export. | | `databricksPipelineStatusTool` | `value` | Public export. | | `databricksPipelineStopTool` | `value` | Public export. | | `DatabricksPrincipal` | `type` | Public export. | | `DatabricksPrincipalEnvOptions` | `type` | Public export. | | `databricksPrincipalFromEnv` | `value` | Resolve a PAT or OAuth M2M principal consistently across CLIs, recipes, Apps, and tests. | | `databricksRagChain` | `value` | Cookbook-style online RAG chain over **native** Mosaic Vector Search + Model Serving. See the package declarations for an example. | | `DatabricksRagChain` | `type` | Public export. | | `DatabricksRagChainOptions` | `type` | Public export. | | `DatabricksRagChainResolvedOptions` | `type` | Inputs once a bundle (or explicit retriever + model) is resolved. | | `DatabricksRequestOptions` | `type` | Public export. | | `DatabricksRequestTags` | `type` | Public export. | | `DatabricksRestClient` | `value` | Public export. | | `databricksRunJobTool` | `value` | Public export. | | `DatabricksRunLifeCycleState` | `type` | Public export. | | `DatabricksRunOutput` | `type` | Public export. | | `DatabricksRunReceipt` | `type` | Public export. | | `DatabricksRunResultState` | `type` | Public export. | | `DatabricksRunState` | `type` | Public export. | | `DatabricksRunTimeoutError` | `value` | Public export. | | `databricksSecretsProvider` | `value` | Databricks Secret Management adapter. Secret values are decoded only at runtime. | | `DatabricksSecretsProviderOptions` | `type` | Public export. | | `databricksSqlTool` | `value` | Public export. | | `DatabricksSqlToolOptions` | `type` | Public export. | | `databricksTableInfoTool` | `value` | Public export. | | `databricksTelemetry` | `value` | Public export. | | `DatabricksTelemetry` | `type` | Public export. | | `DatabricksTelemetryOptions` | `type` | Cost + lineage as core telemetry (v2, workstream C4): submission lifecycle events and governed tool-call lineage land in Lakebase tables keyed by the same `submissionId` that drives events, settlement, and audit — so spend, lineage, and outcomes join on one stable key. Writes are fire-and-forget: a telemetry failure must never affect execution. | | `databricksTenantCostLimit` | `value` | Public export. | | `DatabricksTokenProvider` | `type` | A bearer-token resolver awaited per request. Shape matches what `DatabricksRestClient`, `databricksFoundationModelProvider` (via `tokenProvider`), and the Lakebase store accept, so a single principal threads through model calls, data calls, and state. | | `databricksVectorSearch` | `value` | Databricks Mosaic AI Vector Search as a `Retriever`. Runs under the bundle's UC principal. | | `DatabricksVectorSearchOptions` | `type` | Public export. | | `DatabricksWaitOptions` | `type` | Public export. | | `DatabricksWorkspaceCompatibilityRecord` | `type` | Public export. | | `databricksWorkspaceOrigin` | `value` | Normalize a workspace hostname or URL to its HTTPS origin. | | `databricksWorkspaceSource` | `value` | Public export. | | `DatabricksWorkspaceSourceOptions` | `type` | Public export. | | `defineDatabricksAgent` | `value` | Public export. | | `DefineDatabricksAgentOptions` | `type` | Public export. | | `ensureDatabricksAiGateway` | `value` | Verify that Unity AI Gateway v2 is enabled. A successful empty listing still means the service is available; model-service discovery reports whether usable models exist. | | `ensureDatabricksTelemetryTables` | `value` | Create the telemetry tables when absent. Idempotent. | | `exportAgentEvaluationJsonl` | `value` | Serialize evaluation records as JSONL for MLflow / Agent Evaluation import. | | `exportMlflow3EvaluationJsonl` | `value` | Serialize structured MLflow 3 evaluation rows as newline-delimited JSON. | | `fabricPrincipalFor` | `value` | Map a Databricks principal onto the SDK's `FabricPrincipal` so the governed identity rides submissions → tool calls → lineage/cost records. Never carries a token — ids and labels only. | | `FabricPrincipalOverrides` | `type` | Public export. | | `inferenceTableUsageQuery` | `value` | SQL aggregating a serving endpoint's inference/payload table by `client_request_id` — one row per submission with the request count and the first/last request timestamps — for offline joins against `fh_submission_telemetry.submission_id`. Join contract: callers MUST send the submission id as the serving request's `client_request_id` (e.g. the `client_request_id` field on the serving-endpoint invocation) — the inference table records it verbatim, and this query's `submission_id` column only joins when that convention holds. Rows without a `client_request_id` (traffic from other callers) are... | | `InferenceTableUsageQueryOptions` | `type` | Public export. | | `inspectDatabricksAppUserAuthorization` | `value` | Validate a Databricks Apps forwarded user token and return non-secret identity/lifetime metadata suitable for OBO lifecycle certification. The access token is never returned or included in an error. | | `isDatabricksModelService` | `value` | True for Unity Catalog model-service identifiers accepted by Unity AI Gateway. | | `lakebaseClient` | `value` | Public export. | | `LakebaseClient` | `type` | Public export. | | `LakebaseClientOptions` | `type` | Public export. | | `lakebaseCredentialProvider` | `value` | Exchange a workspace OAuth token for a Lakebase database credential. The result is cached, refreshed early with jitter, and refreshes are single-flight. | | `LakebaseCredentialProviderOptions` | `type` | Public export. | | `LakebasePoolConfig` | `type` | Public export. | | `LakebasePoolFactory` | `type` | Public export. | | `LegacyAgentEvaluationRecord` | `type` | One row in the shape commonly used with Databricks / MLflow GenAI evaluation tables (request / response / retrieved_context). This is an **export** for Mosaic Agent Evaluation and notebooks — not a reimplementation of judges. | | `listDatabricksModelServices` | `value` | List fully-qualified Unity Catalog model services with bounded, loop-safe pagination. | | `Mlflow3RagEvaluationRecord` | `type` | MLflow 3 evaluation-dataset row with structured inputs, outputs, and expectations. | | `mlflowTraceExporter` | `value` | Public export. | | `MlflowTraceExporter` | `type` | Public export. | | `MlflowTraceExporterOptions` | `type` | MLflow trace export (v2 migration, workstream C6): one MLflow trace per settled submission, so agent activity shows up in the workspace's MLflow experiment UI next to models and evaluations. Wiring: `sink` goes into `createSubmissionRunner({ telemetry })` and `onEvent` fans in with the app's `onEvent` callback. A `submission_started` telemetry event opens a span buffer keyed by the store session id (`agent:<agent>:<instanceId>:<session>` — the `sessionId` stamped on `FabricEvent`s during the attempt); every event that matches an open buffer and converts via the S... | | `MlflowTraceInput` | `type` | Public export. | | `MlflowTracePayload` | `type` | Public export. | | `MockDatabricksModelProvider` | `value` | A deterministic `ModelProvider` for Databricks agent tests and init templates. Returns structured responses for SQL/table-info tool calls without requiring real Databricks credentials. | | `MockDatabricksModelProviderOptions` | `type` | Public export. | | `onBehalfOfFromHeaders` | `value` | On-behalf-of principal from Databricks Apps user-authorization headers: the platform forwards the signed-in user's access token as `x-forwarded-access-token` (plus `x-forwarded-email`/`x-forwarded-user`). Returns `undefined` when the request carries no user token (e.g. app service-to-service traffic) so callers fall back to the app principal. | | `queryDatabricksLineageEvidence` | `value` | Join governed object access to the submission actor, outcome, and model/tool cost. | | `RagChainPostProcessOptions` | `type` | Public export. | | `RagChainPromptOptions` | `type` | Public export. | | `RagEvalExpected` | `type` | Public export. | | `RagPreprocess` | `type` | Public export. | | `RagTurn` | `type` | Structured result of one online RAG inference turn. Mirrors the Databricks AI Cookbook chain: preprocess → retrieve (Vector Search) → prompt augment → generate → post-process. Fabric does **not** reimplement Vector Search or Model Serving — this is a thin orchestration over native Databricks APIs already exposed by this package. | | `ragTurnCitationsScorer` | `value` | Public export. | | `ragTurnContainsScorer` | `value` | Adapter for `@fabric-harness/evals` scorers when the suite output is a `RagTurn`. Keep heavy quality judges in Databricks Agent Evaluation; use these for CI smoke. | | `RagTurnScore` | `type` | Public export. | | `reconcileDatabricksCost` | `value` | Compare immediate estimates with delayed System Tables actuals and apply an explicit outage policy. | | `registerDatabricksModelProvider` | `value` | Register `databricks/<model>` model references. | | `resolveToolRefs` | `value` | Public export. | | `runDatabricksCertification` | `value` | Run live capability checks and produce stable, secret-redacted CI evidence. | | `RunDatabricksCertificationOptions` | `type` | Public export. | | `RunDatabricksJobInput` | `type` | Public export. | | `scoreRagTurn` | `value` | Lightweight local checks before/alongside Databricks Agent Evaluation. | | `servingUsageCapture` | `value` | Build an `onEvent` fan-in that turns model-usage events into `submission_usage` telemetry. For every `FabricEvent` carrying `data.usage` (`{ inputTokens?, outputTokens?, costUsd? }`, at least one numeric field) — and optionally `data.model` — while an ambient `currentSubmissionContext` is set, it emits one `submission_usage` event stamped with the submission correlation (submission/attempt ids, agent identity, tenant, actor). Events observed outside a submission context are dropped silently — not every session turn belongs to a submission. The submission context does not carry the... | | `ServingUsageCaptureOptions` | `type` | Serving usage capture (v2 migration, workstream C7): attribute model/serving spend to submissions through the ambient submission context. | | `SubmitDatabricksNotebookInput` | `type` | Public export. | | `toAgentEvaluationRecord` | `value` | Convert a `RagTurn` into a Databricks-friendly evaluation record. Upload JSONL of these rows to MLflow / Agent Evaluation rather than building a parallel judge stack in Fabric. | | `toMlflow3EvaluationRecord` | `value` | Convert a RAG turn into the structured MLflow 3 evaluation-dataset shape. | | `UcVolumesAttachmentStore` | `value` | Public export. | | `UcVolumesAttachmentStoreOptions` | `type` | Unity Catalog Volumes attachment backend (v2 migration, workstream C2). Implements the SDK `AttachmentStore` contract on a UC Volume via the Databricks Files API, so attachment bytes are governed by Unity Catalog grants like every other Databricks asset. Layout under the volume: See the package declarations for an example. The bare `<digest>` file holds the raw bytes; the `<digest>.json` sidecar holds the `AttachmentRef` metadata and doubles as the record's existence marker: `put` is first-write-wins per `(scope, digest)` — when the sidecar already exists the put is a no-op, so... | | `unityCatalogTablesTool` | `value` | Public export. | | `waitForDatabricksRun` | `value` | Public export. | | `waitForDatabricksStatement` | `value` | Public export. | | `withGovernance` | `value` | Wraps one Databricks tool to stamp lineage/audit and enforce the optional catalog allowlist. | | `withGovernanceTools` | `value` | Wraps a set of Databricks tools with `withGovernance`. | ### `@fabric-harness/databricks/agent` | Export | Kind | Summary | | --- | --- | --- | | `databricksAgent` | `value` | Public export. | | `DatabricksAgentOptions` | `type` | Public export. | | `defineDatabricksAgent` | `value` | Public export. | | `DefineDatabricksAgentOptions` | `type` | Public export. | | `resolveToolRefs` | `value` | Public export. | ### `@fabric-harness/databricks/sql-sandbox` | Export | Kind | Summary | | --- | --- | --- | | `databricksSqlSandbox` | `value` | Public export. | | `DatabricksSqlSandboxOptions` | `type` | Sandbox backend that maps `exec(command)` to a Databricks SQL Statement Execution API call against a SQL Warehouse. Useful for data agents whose "shell" is a query interface. Behavior: - `exec(sql)` runs the SQL synchronously (with a timeout) and returns the serialized result set as `stdout` (newline-delimited JSON rows by default, or CSV when `resultFormat: 'csv'`). - File operations are intentionally minimal: the sandbox-virtual filesystem keeps a small in-memory map. SQL warehouses are not file servers; mount actual data via `databricksVolumeSource` from `@fabric-harness/connectors`. Usa... | ## @fabric-harness/node ### `@fabric-harness/node` | Export | Kind | Summary | | --- | --- | --- | | `AgentDescription` | `type` | Public export. | | `AgentModule` | `type` | Public export. | | `AgentNotFoundError` | `value` | Public export. | | `AgentSummary` | `type` | Public export. | | `applyEnvModelProvider` | `value` | Public export. | | `ApprovalSummary` | `type` | Public export. | | `asJsonObject` | `value` | Public export. | | `assertDataResidency` | `value` | Public export. | | `BackupObjectStore` | `type` | Public export. | | `backupPostgresPersistence` | `value` | Create a transactionally consistent logical backup and write it to object storage. | | `BuildBundleStrategy` | `type` | Public export. | | `BuildManifest` | `type` | Public export. | | `BuildManifestAgent` | `type` | Public export. | | `BuildManifestFile` | `type` | Public export. | | `BuildManifestRole` | `type` | Public export. | | `BuildManifestSkill` | `type` | Public export. | | `BuildSummary` | `type` | Public export. | | `BuildTarget` | `type` | Public export. | | `buildWorkspace` | `value` | Public export. | | `BuildWorkspaceOptions` | `type` | Public export. | | `BuildWorkspaceResult` | `type` | Public export. | | `cancelSessionTask` | `value` | Public export. | | `cancelTaskInStore` | `value` | Public export. | | `CheckpointSummary` | `type` | Public export. | | `compactPersistedSession` | `value` | Public export. | | `CompactPersistedSessionOptions` | `type` | Public export. | | `compactSessionInStore` | `value` | Public export. | | `createConfiguredSessionStore` | `value` | Public export. | | `CreateConfiguredSessionStoreOptions` | `type` | Public export. | | `createFabricRunContext` | `value` | Public export. | | `CreateFabricRunContextOptions` | `type` | Public export. | | `createMcpHttpServer` | `value` | Expose governed Harness tools through stateless MCP Streamable HTTP. | | `createPersistentDispatchProcessor` | `value` | Build a `DispatchProcessor` that applies dispatched inputs to a persistent instance session. Idempotent by `dispatchId`: a marker entry is appended to the instance session after a dispatch is applied, and re-delivery of the same `dispatchId` is skipped. The marker lives in the shared session store, so dedup holds across separate processings (and survives restart when the store is durable). | | `createPersistentSubmissionExecutor` | `value` | The `SubmissionExecutor` that applies durable submissions to persistent instance sessions. `execute` runs a real session turn (`session.prompt` is idempotent by submission id, so a recovered attempt resumes instead of double-applying its input); everything else operates store-level so reconciliation never needs a live agent. | | `createPrivateNetworkFetch` | `value` | Create an outbound Node client with one policy and transport trust path. Credentials and PEM material remain in the dispatcher and are never placed in request URLs, Fabric events, or model messages. | | `currentMcpRequestContext` | `value` | Public export. | | `databricksAppsOidcAuthenticator` | `value` | Databricks workspace OIDC preset for Apps ingress and workspace-scoped RBAC claims. | | `DatabricksAppsOidcAuthenticatorOptions` | `type` | Public export. | | `daytona` | `value` | One-call wiring for a Daytona-native agent: resolves the model provider from config or env, includes Daytona-specific workspace tools, and sets a safe default egress policy. | | `daytonaAgent` | `value` | Public export. | | `DaytonaAgentOptions` | `type` | Public export. | | `DaytonaBundle` | `type` | Public export. | | `DaytonaBundleConfig` | `type` | Public export. | | `defineApplication` | `value` | Identity helper that preserves route/middleware inference in workspace config. | | `defineDaytonaAgent` | `value` | Public export. | | `DefineDaytonaAgentOptions` | `type` | Public export. | | `defineDockerAgent` | `value` | Public export. | | `DefineDockerAgentOptions` | `type` | Public export. | | `defineE2bAgent` | `value` | Public export. | | `DefineE2bAgentOptions` | `type` | Public export. | | `defineK8sAgent` | `value` | Public export. | | `DefineK8sAgentOptions` | `type` | Public export. | | `defineModalAgent` | `value` | Public export. | | `DefineModalAgentOptions` | `type` | Public export. | | `defineNodeAgent` | `value` | Public export. | | `DefineNodeAgentOptions` | `type` | Public export. | | `DeletionEvidenceSigner` | `type` | Public export. | | `DeletionEvidenceStore` | `type` | Public export. | | `describeAgentFile` | `value` | Public export. | | `DevServerHandle` | `type` | Public export. | | `DevServerOptions` | `type` | Public export. | | `discoverWorkspace` | `value` | Public export. | | `docker` | `value` | One-call wiring for a Docker-native agent: resolves the model provider from config or env, includes Docker-specific container tools, and sets a safe default egress policy. | | `dockerAgent` | `value` | Public export. | | `DockerAgentOptions` | `type` | Public export. | | `DockerBundle` | `type` | Public export. | | `DockerBundleConfig` | `type` | Public export. | | `e2b` | `value` | One-call wiring for an E2B-native agent: resolves the model provider from config or env, includes built-in tools, and sets a safe default egress policy. | | `e2bAgent` | `value` | Public export. | | `E2bAgentOptions` | `type` | Public export. | | `E2bBundle` | `type` | Public export. | | `E2bBundleConfig` | `type` | Public export. | | `ed25519DeletionEvidenceSigner` | `value` | Public export. | | `enforcePersistenceRetention` | `value` | Enforce durable data retention. Use a deletion-evidence-wrapped bundle for signed session receipts. | | `ensurePostgresAttachmentTables` | `value` | Create the attachments table when absent. Idempotent. | | `ensurePostgresConversationStreamTables` | `value` | Create the conversation stream tables when absent. Idempotent. | | `ensurePostgresSubmissionTables` | `value` | Create the submission tables and indexes when absent. Idempotent. | | `ensureSqliteAttachmentTables` | `value` | Create the attachments table when absent. Idempotent. | | `ensureSqliteConversationStreamTables` | `value` | Create the conversation stream tables when absent. Idempotent. | | `ensureSqliteSubmissionTables` | `value` | Create the submission tables and indexes when absent. Idempotent. | | `entraIdAuthenticator` | `value` | Microsoft Entra ID v2 preset with tenant, roles, groups, and app/user identity mapping. | | `EntraIdAuthenticatorOptions` | `type` | Public export. | | `ExternalRetentionTarget` | `type` | Public export. | | `FabricApplication` | `type` | Public export. | | `FabricApplicationMiddleware` | `type` | Public export. | | `FabricApplicationNext` | `type` | Public export. | | `FabricApplicationPublicMiddleware` | `type` | Public export. | | `FabricApplicationPublicRequestContext` | `type` | Public export. | | `FabricApplicationRequestContext` | `type` | Public export. | | `FabricApplicationRoute` | `type` | Public export. | | `FabricApplicationStores` | `type` | Public export. | | `FabricBuildContext` | `type` | Public export. | | `FabricBuildError` | `value` | Public export. | | `FabricBuildErrorCode` | `type` | Public export. | | `FabricBuildPlugin` | `type` | Public export. | | `FabricHarnessConfig` | `type` | Public export. | | `FabricHarnessDatabricksConfig` | `type` | Public export. | | `FabricHarnessDatabricksServingConfig` | `type` | Public export. | | `FabricHarnessEnvironmentConfig` | `type` | Public export. | | `FabricHarnessPersistenceConfig` | `type` | Public export. | | `FabricHarnessSandboxConfig` | `type` | Public export. | | `FabricHarnessStoreConfig` | `type` | Public export. | | `FabricHarnessTemporalConfig` | `type` | Public export. | | `FabricMcpHttpServer` | `type` | Public export. | | `FabricMcpRequestContext` | `type` | Public export. | | `FabricPersistence` | `type` | Public export. | | `FabricPersistenceError` | `value` | Public export. | | `FabricPersistenceErrorCode` | `type` | Public export. | | `FabricPersistenceHealth` | `type` | Public export. | | `fabricPostgresMigrations` | `value` | Public export. | | `FabricRetentionPolicy` | `type` | Public export. | | `FileAttachmentStore` | `value` | Public export. | | `FileAttachmentStoreOptions` | `type` | Public export. | | `FileConversationStreamStore` | `value` | Public export. | | `FileConversationStreamStoreOptions` | `type` | Public export. | | `FileSessionStore` | `value` | Public export. | | `FileSessionStoreOptions` | `type` | Public export. | | `findWorkspaceRoot` | `value` | Public export. | | `forkSessionAtStep` | `value` | Forks a session at the given step into a new session in the same store. The new session contains the active-path entries up to and including `stepId`, and its `leafId` is set to `stepId`. Used by `fh replay --rerun`. If a `user_prompt` entry exists immediately after the cut on the original active path, its text is returned as `resumePromptText` so the caller can resubmit it against the forked session. | | `ForkSessionAtStepResult` | `type` | Public export. | | `getMetricsFromStore` | `value` | Public export. | | `getRequiredAgentDefinition` | `value` | Public export. | | `getSessionApprovalState` | `value` | Public export. | | `getSessionArtifact` | `value` | Public export. | | `getSessionMetrics` | `value` | Public export. | | `getSessionTask` | `value` | Public export. | | `getSessionTimeline` | `value` | Public export. | | `getTaskFromStore` | `value` | Public export. | | `hmacDeletionEvidenceSigner` | `value` | Public export. | | `httpBackupObjectStore` | `value` | HTTP PUT/GET object store for presigned S3/R2, Azure Blob SAS, or an internal object gateway. | | `HttpRateLimitClass` | `type` | Public export. | | `HttpRateLimitConfig` | `type` | Public export. | | `HttpRateLimitContext` | `type` | Public export. | | `HttpRateLimitDecision` | `type` | Public export. | | `HttpRateLimiter` | `type` | Public export. | | `HttpRateLimitRule` | `type` | Public export. | | `inspectReplay` | `value` | Public export. | | `inspectReplayFromStore` | `value` | Public export. | | `inspectSession` | `value` | Public export. | | `inspectSessionFromStore` | `value` | Public export. | | `isDefinedAgent` | `value` | Public export. | | `isPersistentAgent` | `value` | Public export. | | `JobScheduler` | `type` | Public export. | | `JobSchedulerOptions` | `type` | Public export. | | `k8s` | `value` | One-call wiring for a Kubernetes-native agent: resolves the model provider from config or env, includes K8s-specific kubectl tools, and sets a safe default egress policy. | | `k8sAgent` | `value` | Public export. | | `K8sAgentOptions` | `type` | Public export. | | `K8sBundle` | `type` | Public export. | | `K8sBundleConfig` | `type` | Public export. | | `libsqlPersistence` | `value` | Full local libSQL or remote Turso bundle using optimistic version fencing. | | `LibSqlPersistenceClient` | `type` | Public export. | | `LibSqlPersistenceOptions` | `type` | Public export. | | `LibSqlResultSet` | `type` | Public export. | | `listAgentFiles` | `value` | Public export. | | `listAgentSummaries` | `value` | Public export. | | `listApprovalsFromStore` | `value` | Public export. | | `listApprovalStatesFromStore` | `value` | Public export. | | `listBuilds` | `value` | Public export. | | `listCheckpointsFromStore` | `value` | Public export. | | `listSessionApprovals` | `value` | Public export. | | `listSessionApprovalStates` | `value` | Public export. | | `listSessionArtifacts` | `value` | Public export. | | `listSessionCheckpoints` | `value` | Public export. | | `listSessions` | `value` | Public export. | | `listSessionSummariesFromStore` | `value` | Public export. | | `listSessionTasks` | `value` | Public export. | | `listTasksFromStore` | `value` | Public export. | | `loadAgentModule` | `value` | Public export. | | `LoadAgentModuleOptions` | `type` | Public export. | | `loadFabricHarnessConfig` | `value` | Public export. | | `LoadFabricHarnessConfigOptions` | `type` | Public export. | | `loadRoles` | `value` | Public export. | | `loadSkills` | `value` | Public export. | | `mapOidcPrincipal` | `value` | Public export. | | `memoryDeletionEvidenceStore` | `value` | Public export. | | `memoryPersistence` | `value` | Infrastructure-free unified bundle for tests and lightweight applications. | | `memorySchedulerLeaseStore` | `value` | Public export. | | `migratePostgresPersistence` | `value` | Public export. | | `MockDaytonaModelProvider` | `value` | A deterministic `ModelProvider` for Daytona agent tests and init templates. Returns structured responses without requiring real API credentials. | | `MockDaytonaModelProviderOptions` | `type` | Public export. | | `MockDockerModelProvider` | `value` | A deterministic `ModelProvider` for Docker agent tests and init templates. Returns structured responses without requiring real API credentials. | | `MockDockerModelProviderOptions` | `type` | Public export. | | `MockE2bModelProvider` | `value` | A deterministic `ModelProvider` for E2B agent tests and init templates. Returns structured responses without requiring real API credentials. | | `MockE2bModelProviderOptions` | `type` | Public export. | | `MockK8sModelProvider` | `value` | A deterministic `ModelProvider` for Kubernetes agent tests and init templates. Returns structured responses without requiring real API credentials. | | `MockK8sModelProviderOptions` | `type` | Public export. | | `MockModalModelProvider` | `value` | A deterministic `ModelProvider` for Modal agent tests and init templates. Returns structured responses without requiring real API credentials. | | `MockModalModelProviderOptions` | `type` | Public export. | | `MockNodeModelProvider` | `value` | A deterministic `ModelProvider` for Node agent tests and init templates. Returns structured responses without requiring real API credentials. | | `MockNodeModelProviderOptions` | `type` | Public export. | | `modal` | `value` | One-call wiring for a Modal-native agent: resolves the model provider from config or env, includes built-in tools, and sets a safe default egress policy. | | `modalAgent` | `value` | Public export. | | `ModalAgentOptions` | `type` | Public export. | | `ModalBundle` | `type` | Public export. | | `ModalBundleConfig` | `type` | Public export. | | `mongodbPersistence` | `value` | Full bundle over a MongoDB collection using `_id` + version compare-and-swap. | | `MongoPersistenceClient` | `type` | Public export. | | `MongoPersistenceCollection` | `type` | Public export. | | `MongoPersistenceOptions` | `type` | Public export. | | `mysqlPersistence` | `value` | Full bundle over a MySQL 8 compatible database using version-fenced snapshot rows. | | `MySqlPersistenceClient` | `type` | Public export. | | `MySqlPersistenceOptions` | `type` | Public export. | | `nextScheduledAt` | `value` | Public export. | | `node` | `value` | One-call wiring for a Node-native agent: resolves the model provider from config or env, includes built-in file/shell tools, and sets a safe default egress policy. | | `nodeAgent` | `value` | Public export. | | `NodeAgentOptions` | `type` | Public export. | | `NodeBundle` | `type` | Public export. | | `NodeBundleConfig` | `type` | Public export. | | `OidcClaimMapping` | `type` | Public export. | | `oidcJwtAuthenticator` | `value` | Validate Bearer JWTs with a local or remote JWKS and map claims into server RBAC. | | `OidcJwtAuthenticatorOptions` | `type` | Public export. | | `OidcPrincipalContext` | `type` | Public export. | | `parseCookie` | `value` | Parse the `Cookie` header on an IncomingMessage and return a named cookie's value, or undefined when missing. Use inside a custom `extractAuthToken` to validate session cookies set by your existing identity layer. | | `parseFrontmatter` | `value` | Public export. | | `pathExists` | `value` | Public export. | | `PersistedCompactionResult` | `type` | Public export. | | `PersistentDispatchProcessorOptions` | `type` | Public export. | | `PersistentPromptOptions` | `type` | Public export. | | `PersistentPromptResult` | `type` | Public export. | | `PersistentSessionBusyError` | `value` | Public export. | | `PersistentSubmissionExecutorOptions` | `type` | Public export. | | `PostgresAttachmentStore` | `value` | Public export. | | `PostgresAttachmentStoreOptions` | `type` | Public export. | | `PostgresBackupRecord` | `type` | Public export. | | `PostgresClientLike` | `type` | Public export. | | `PostgresConversationStreamStore` | `value` | Public export. | | `PostgresConversationStreamStoreOptions` | `type` | Public export. | | `postgresCostBudgetStore` | `value` | Postgres-backed cost budget store. Pair with `init({ costLimit: { perScope, scopeKey, store } })` to enforce a budget that survives process restarts — per-tenant, per-day, per-organization caps, etc. The table holds one row per scope key: `(scope TEXT PRIMARY KEY, total_usd NUMERIC, updated_at TIMESTAMPTZ)`. Increments use ON CONFLICT UPDATE for atomic compare-and-set semantics. | | `PostgresCostBudgetStoreOptions` | `type` | Public export. | | `postgresDeletionEvidenceStore` | `value` | Public export. | | `PostgresMigration` | `type` | Public export. | | `PostgresMigrationResult` | `type` | Public export. | | `postgresPersistence` | `value` | One Postgres/Lakebase bundle for every durable Node server store. | | `PostgresPersistenceOptions` | `type` | Public export. | | `PostgresRestoreResult` | `type` | Public export. | | `postgresSandboxOwnershipLeaseStore` | `value` | Atomic, expiry-aware ownership leases for portable sandboxes. | | `postgresSchedulerLeaseStore` | `value` | Postgres-backed scheduler leases for horizontally scaled Node deployments. | | `postgresSessionMemory` | `value` | Postgres-backed `SessionMemory`. Persistent across process restarts; scoped by tenantId. Pair with `init({ memory })` to share recall across a fleet of agents. Schema: CREATE TABLE fabric_harness_session_memory ( tenant_id TEXT NOT NULL DEFAULT '', key TEXT NOT NULL, value JSONB NOT NULL, metadata JSONB, expires_at TIMESTAMPTZ, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (tenant_id, key) ); Set/Get scrub expired rows lazily on access. Run a scheduled `DELETE WHERE expires_at < NOW()` if you have many TTL'd entries. | | `PostgresSessionMemoryOptions` | `type` | Public export. | | `PostgresSessionStore` | `value` | Public export. | | `PostgresSessionStoreOptions` | `type` | Public export. | | `PostgresSubmissionStore` | `value` | Public export. | | `PostgresSubmissionStoreOptions` | `type` | Public export. | | `principalHasPermission` | `value` | Public export. | | `principalToActor` | `value` | Public export. | | `principalToApprovalActor` | `value` | Public export. | | `PrivateNetworkFetchClient` | `type` | Public export. | | `PrivateNetworkFetchOptions` | `type` | Public export. | | `PrivateNetworkProxyOptions` | `type` | Public export. | | `PrivateNetworkTlsOptions` | `type` | Public export. | | `readBuildManifest` | `value` | Public export. | | `redisApprovalNotificationStore` | `value` | Distributed dedupe state for `approvalNotificationHandler()`. | | `RedisApprovalNotificationStoreOptions` | `type` | Public export. | | `RedisClientLike` | `type` | Cross-process token-bucket rate limiter backed by Redis. Pair with `OpenAICompatibleModelProvider` / `AnthropicModelProvider` to enforce a shared API-key quota across a fleet of containers. `RedisClientLike` only requires an `eval` method — both `ioredis` and `@upstash/redis` ship a compatible signature. fabric-harness does NOT depend on either; users pass their own client. | | `redisHttpRateLimiter` | `value` | Creates an atomic, cross-process HTTP limiter for `startDevServer`. The caller supplies its Redis client so the Node package has no Redis SDK dependency. | | `RedisHttpRateLimiterOptions` | `type` | Public export. | | `redisPersistence` | `value` | Redis/Valkey bundle with cluster-safe keys, binary attachments, and optional retention TTL. | | `RedisPersistenceClient` | `type` | Public export. | | `RedisPersistenceOptions` | `type` | Public export. | | `redisRateLimiter` | `value` | Public export. | | `RedisRateLimiterOptions` | `type` | Public export. | | `renderOperatorConsole` | `value` | Public export. | | `ReplayInspection` | `type` | Public export. | | `resolveAgentPath` | `value` | Public export. | | `resolveApprovalInStore` | `value` | Public export. | | `resolveConfigPath` | `value` | Public export. | | `resolveDaytonaToolRefs` | `value` | Public export. | | `resolveDockerToolRefs` | `value` | Public export. | | `resolveE2bToolRefs` | `value` | Public export. | | `resolveK8sToolRefs` | `value` | Public export. | | `resolveModalToolRefs` | `value` | Public export. | | `resolvePrincipalTenant` | `value` | Public export. | | `resolveServerPermission` | `value` | Public export. | | `resolveSessionApproval` | `value` | Public export. | | `resolveSseKeepaliveMs` | `value` | Resolve the SSE keepalive interval from the environment. Returns the parsed `FABRIC_HARNESS_SSE_KEEPALIVE_MS` value, or the 15000ms default. Negative or unparseable values fall back to the default. Returns `0` when explicitly set to `0`, disabling keepalive. | | `resolveToolRefs` | `value` | Public export. | | `restorePostgresPersistence` | `value` | Verify and restore a logical backup under an exclusive advisory lock and transaction. | | `RetentionResult` | `type` | Public export. | | `RetentionRule` | `type` | Public export. | | `runAgent` | `value` | Public export. | | `RunAgentOptions` | `type` | Public export. | | `RunAgentResult` | `type` | Public export. | | `runPersistentPrompt` | `value` | Public export. | | `runPostgresRecoveryDrill` | `value` | Public export. | | `SandboxOwnershipPostgresClient` | `type` | Public export. | | `SchedulerLeaseStore` | `type` | Public export. | | `SchedulerPostgresClient` | `type` | Public export. | | `ServerAuthorizationContext` | `type` | Public export. | | `ServerPermission` | `type` | Public export. | | `ServerPrincipal` | `type` | Public export. | | `SessionMetrics` | `type` | Public export. | | `sessionRunStore` | `value` | Public export. | | `SessionSummary` | `type` | Public export. | | `SessionTimeline` | `type` | Public export. | | `SqliteAttachmentStore` | `value` | Public export. | | `SqliteAttachmentStoreOptions` | `type` | Public export. | | `SqliteConversationStreamStore` | `value` | Public export. | | `SqliteConversationStreamStoreOptions` | `type` | Public export. | | `SqliteCostBudgetStore` | `value` | Atomic cross-process cost totals for a single-node SQLite deployment. | | `SqliteCostBudgetStoreOptions` | `type` | Public export. | | `SqliteDatabaseLike` | `type` | The slice of `node:sqlite`'s `DatabaseSync` this store uses. | | `sqlitePersistence` | `value` | Unified durable bundle for local and single-node deployments. | | `SqlitePersistenceOptions` | `type` | Public export. | | `SQLiteSessionStore` | `value` | Public export. | | `SQLiteSessionStoreOptions` | `type` | Public export. | | `SqliteSubmissionStore` | `value` | Public export. | | `SqliteSubmissionStoreOptions` | `type` | Public export. | | `SSE_KEEPALIVE_DEFAULT_MS` | `value` | Public export. | | `startDevServer` | `value` | Public export. | | `startJobScheduler` | `value` | Start an in-process cron scheduler for finite job `triggers.schedule` values. | | `TaskStatus` | `type` | Public export. | | `TaskSummary` | `type` | Public export. | | `TimelineItem` | `type` | Public export. | | `transpileAgent` | `value` | Public export. | | `TranspileAgentOptions` | `type` | Public export. | | `vaultSecretProvider` | `value` | HashiCorp Vault KV v2 provider. Vault tokens remain in the adapter closure. | | `VaultSecretProviderOptions` | `type` | Public export. | | `verifyAttestation` | `value` | Public export. | | `verifyProvenance` | `value` | Public export. | | `withDeletionEvidence` | `value` | Add signed, append-only deletion evidence to any persistence bundle. | | `withPostgresConnection` | `value` | Public export. | | `WorkspaceInfo` | `type` | Public export. | ### `@fabric-harness/node/agent` | Export | Kind | Summary | | --- | --- | --- | | `defineNodeAgent` | `value` | Public export. | | `DefineNodeAgentOptions` | `type` | Public export. | | `node` | `value` | One-call wiring for a Node-native agent: resolves the model provider from config or env, includes built-in file/shell tools, and sets a safe default egress policy. | | `nodeAgent` | `value` | Public export. | | `NodeAgentOptions` | `type` | Public export. | | `NodeBundle` | `type` | Public export. | | `NodeBundleConfig` | `type` | Public export. | | `resolveToolRefs` | `value` | Public export. | ### `@fabric-harness/node/docker-agent` | Export | Kind | Summary | | --- | --- | --- | | `defineDockerAgent` | `value` | Public export. | | `DefineDockerAgentOptions` | `type` | Public export. | | `docker` | `value` | One-call wiring for a Docker-native agent: resolves the model provider from config or env, includes Docker-specific container tools, and sets a safe default egress policy. | | `dockerAgent` | `value` | Public export. | | `DockerAgentOptions` | `type` | Public export. | | `DockerBundle` | `type` | Public export. | | `DockerBundleConfig` | `type` | Public export. | | `resolveDockerToolRefs` | `value` | Public export. | ### `@fabric-harness/node/k8s-agent` | Export | Kind | Summary | | --- | --- | --- | | `defineK8sAgent` | `value` | Public export. | | `DefineK8sAgentOptions` | `type` | Public export. | | `k8s` | `value` | One-call wiring for a Kubernetes-native agent: resolves the model provider from config or env, includes K8s-specific kubectl tools, and sets a safe default egress policy. | | `k8sAgent` | `value` | Public export. | | `K8sAgentOptions` | `type` | Public export. | | `K8sBundle` | `type` | Public export. | | `K8sBundleConfig` | `type` | Public export. | | `resolveK8sToolRefs` | `value` | Public export. | ## @fabric-harness/sdk ### `@fabric-harness/sdk` | Export | Kind | Summary | | --- | --- | --- | | `actionAsTool` | `value` | Expose an action to the model as a tool on an agent profile. The tool's JSON schema comes from the action's input schema; execution validates input/output exactly like `runAction`. | | `ActionContext` | `type` | Context passed to an action's `run`. Unlike a tool's `execute` (a leaf capability invoked by the model), an action receives the harness itself and can orchestrate: prompt models, spawn sessions, call other actions. | | `ActionDefinition` | `type` | A named, schema-validated unit of harness work created with `defineAction`. First-class in v2: registrable on agent profiles as a tool, callable from jobs and agents, evaluable via `fh test`, and deployable as a platform job task. | | `ActionError` | `value` | Public export. | | `ActionHost` | `type` | What an action runs against: the harness entry point (`init` can prompt models, spawn sessions, and call tools) plus the platform environment. Jobs, agents, and servers all satisfy this — it is the harness slice of `FabricContext`. | | `ActionOptions` | `type` | Public export. | | `ActivateSkillInput` | `type` | Public export. | | `ActorIdentity` | `type` | Public export. | | `ActualCostSource` | `type` | External source for actual (non-estimated) spend. Implementations can query provider billing APIs, Databricks usage tables, or other real-time cost data. | | `admitSubmissionWithBackend` | `value` | Shared submission admission algorithm for row-oriented backends: dispatch-receipt check → insert-or-ignore → read-back → payload compare (idempotent replay vs. conflict). The message payload is stored as JSON verbatim; payload identity is deep JSON equality, so a backend that normalizes stored JSON (e.g. Postgres JSONB key ordering) still recognizes an exact replay. The caller owns transaction scoping — invoke this inside one transaction and pass callbacks bound to it. When every callback is synchronous the result is returned synchronously, so the algorithm also fits synchronous backends. | | `agent` | `value` | Defaults-injecting agent builder. Exported as `agent` from the bare `@fabric-harness/sdk` entry point. Wraps `init()` so the headless defaults are applied unless the caller overrides them: - `runtime: 'stateless'` - `sandbox: 'virtual'` - `loopRuntime: pi-agent-core` - `compaction: { enabled: true }` If `runtime: 'temporal'` is selected from this builder, a one-time `console.warn` is emitted because auto-compaction is non-deterministic across Temporal replay. Use `@fabric-harness/sdk/strict` for Temporal. | | `AgentAttemptMarker` | `type` | Harness-owned durable evidence that a submission attempt was started and has not yet settled. A coordinator inserts a marker immediately before starting an attempt and deletes it when the attempt settles; reconciliation treats a fresh marker as proof that the attempt may still be running and must not be reconciled as interrupted. | | `AgentDefinition` | `type` | Public export. | | `AgentDispatchAdmission` | `type` | Public export. | | `AgentDispatchReceipt` | `type` | Public export. | | `AgentDispatchRequest` | `type` | Async delivery request to a persistent agent instance + session. | | `AgentEvent` | `type` | Public export. | | `AgentEventBase` | `type` | Common envelope shared by every event variant. | | `AgentEventCallback` | `type` | Callback signature accepted by `init({ onEvent })`, `agent.session(id, { onEvent })`, and `session.prompt(text, { onEvent })`. | | `AgentEventType` | `type` | Public export. | | `AgentInit` | `type` | Public export. | | `AgentLoopRuntime` | `type` | Public export. | | `AgentProfile` | `type` | Public export. | | `AgentProfileOptions` | `type` | Public export. | | `AgentSubmission` | `type` | Public export. | | `AgentSubmissionDurability` | `type` | Public export. | | `AgentSubmissionInput` | `type` | One admitted agent submission — the persisted operational payload for both transports. `kind` records how the submission arrived (`'dispatch'` via `dispatch()`, `'direct'` via the agent HTTP route); a dispatch's `submissionId` is the public `dispatchId` from its receipt. | | `AgentSubmissionStatus` | `type` | Public export. | | `AgentSubmissionStore` | `type` | Durable submission lifecycle storage. Stability: the lease method group mirrors the durable-execution engine and is subject to change until 1.0. This applies to every backend equally. | | `AgentTriggers` | `type` | Public export. | | `aiGateway` | `value` | Generic OpenAI-compatible AI gateway helper. Use for **any** gateway that speaks the OpenAI Chat Completions request/response shape: Helicone, Portkey, LiteLLM (self-hosted), Cloudflare AI Gateway, internal corp proxies, etc. See the package declarations for an example. For Vercel AI Gateway, prefer the `vercelAIGateway` preset — same factory under the hood with the gateway URL pre-baked. | | `AIGatewayOptions` | `type` | Public export. | | `AnthropicModelProvider` | `value` | Public export. | | `AnthropicProviderOptions` | `type` | Public export. | | `applyEstimatedCost` | `value` | Idempotently populate `usage.costUsd` from the static price table. Mutates `usage` and returns it. No-op when: - `usage` is undefined, - `usage.costUsd` is already set (provider supplied it directly), - no price row matches `modelRef`. | | `ApprovalCallback` | `type` | Public export. | | `ApprovalDecision` | `type` | Public export. | | `ApprovalNotification` | `type` | Public export. | | `ApprovalNotificationDeadLetter` | `type` | Public export. | | `ApprovalNotificationDeliveryStore` | `type` | Public export. | | `approvalNotificationFromEvent` | `value` | Public export. | | `approvalNotificationHandler` | `value` | Convert approval events into retryable, deduplicated notifications. This callback never throws. | | `ApprovalNotificationHandlerOptions` | `type` | Public export. | | `ApprovalNotificationState` | `type` | Public export. | | `ApprovalNotifier` | `type` | Public export. | | `ApprovalOptions` | `type` | Public export. | | `ApprovalPolicyRule` | `type` | Per-pattern approval metadata. Lets policy authors route specific tools/commands to a named audience (e.g. `'reviewer'`, `'compliance-team'`, `'project-admin'`). The audience id is opaque to fabric-harness — host applications map ids to humans via their own identity layer. | | `ApprovalRequest` | `type` | Public export. | | `ApprovalResponse` | `type` | Public export. | | `ApprovalRisk` | `type` | Public export. | | `ApprovalState` | `type` | Public export. | | `approvalStatesFromEntries` | `value` | Public export. | | `ApprovalStateStatus` | `type` | Public export. | | `ApprovalUnavailableStrategy` | `type` | Public export. | | `ApprovalVote` | `type` | Public export. | | `ArtifactCreateOptions` | `type` | Public export. | | `ArtifactRef` | `type` | Public export. | | `assertEnforceableNetworkPolicy` | `value` | Refuse production network policy when it can be bypassed by code in the sandbox. This validates an operator assertion; the named network boundary must still be provisioned by Docker, Kubernetes, or the cloud provider. | | `attachmentDigest` | `value` | Lowercase hex SHA-256 of the bytes (WebCrypto). | | `AttachmentLimitError` | `value` | Public export. | | `AttachmentPutInput` | `type` | Public export. | | `AttachmentRef` | `type` | Content-addressed descriptor of one stored attachment. | | `AttachmentStore` | `type` | Durable content-addressed attachment storage. - `put` is idempotent by `(scope, digest)` — re-putting the same content succeeds without duplicating storage (the first stored ref metadata is retained). It MUST verify the digest against the bytes and reject a mismatch with `AttachmentStoreError('DIGEST_MISMATCH')` before writing. - `get`/`getByAttachmentId` return `null` on a miss. - `delete` removes one stored attachment and throws `AttachmentStoreError('NOT_FOUND')` when nothing is stored under `(scope, digest)`. | | `AttachmentStoreError` | `value` | Public export. | | `attachSandbox` | `value` | Build a `SandboxFactory` that, when invoked, returns an `AttachedSandboxEnv` delegating to the registered sandbox without owning its lifecycle. Calling `cleanup()` on the attached env unregisters this attachment but does NOT tear down the underlying sandbox. Pass an in-process `SandboxRef` to attach within the same process, or a `SerializedSandboxRef` (from `session.sandboxRef({ portable: true })`) to rehydrate a sandbox handed off from another process. Cross-process refs require a decoder registered for `serialized.provider` via `registerSandboxRefDecoder()`. | | `AttachSandboxOptions` | `type` | Public export. | | `AttributionQuery` | `type` | Query filters for retrieving aggregated cost attribution rows. | | `AutonomyMode` | `type` | Public export. | | `AutonomyOptions` | `type` | Public export. | | `AzureOpenAIModelProvider` | `value` | Public export. | | `AzureOpenAIProviderOptions` | `type` | Public export. | | `BashInput` | `type` | Public export. | | `bashTool` | `value` | Public export. | | `BedrockModelProvider` | `value` | Public export. | | `BedrockProviderOptions` | `type` | Public export. | | `buildModelMessagesFromHistory` | `value` | Public export. | | `buildResultFollowUpPrompt` | `value` | Follow-up prompt sent when the LLM ends a turn without calling `finish` or `give_up`. | | `buildResultFooter` | `value` | Footer appended to user prompts/skill bodies when a `result` schema is set. | | `buildResultRetryPrompt` | `value` | Public export. | | `BuiltinFileTool` | `type` | Public export. | | `BuiltinTool` | `type` | Public export. | | `bytesToHex` | `value` | Public export. | | `CapabilityPolicy` | `type` | Public export. | | `CartesiaSttProvider` | `value` | Public export. | | `CartesiaSttProviderOptions` | `type` | Public export. | | `CartesiaTtsProvider` | `value` | Public export. | | `CartesiaTtsProviderOptions` | `type` | Public export. | | `chainSecretProviders` | `value` | Resolve from providers in order; errors fail closed instead of falling through. | | `Channel` | `type` | Public export. | | `ChannelContext` | `type` | Public export. | | `ChannelDispatch` | `type` | Public export. | | `ChannelDispatchRequest` | `type` | Public export. | | `ChannelRoute` | `type` | Channels turn platform webhooks (Slack, GitHub, …) into agent dispatches. Handlers are written against the Web `Request`/`Response` API and `crypto.subtle`, so the same channel runs on Node and Cloudflare. A channel is a stateless route container plus a conversation-id (de)serializer — session continuity falls out of the key (same thread → same key → same session). | | `CheckpointCreateOptions` | `type` | Public export. | | `CheckpointRestoreOptions` | `type` | Public export. | | `CheckpointResult` | `type` | Public export. | | `claimSandboxOwnership` | `value` | Claim exclusive ownership of an already-connected portable sandbox. | | `clampCommandTimeout` | `value` | Public export. | | `clampReadLimit` | `value` | Public export. | | `classifySubmissionState` | `value` | Classify how far a persisted submission input progressed. - `absent` — the input entry never landed in session history: the attempt crashed before applying it. Safe to requeue for a clean first attempt. - `completed` — finished work: a canonical settlement entry exists, an assistant response follows the input with no unresolved trailing tool batch, or a later user input shows the conversation moved on. Settle as success; never retry (retrying completed work is the one unrecoverable corruption). - `continuable` — the trailing turn carries unresolved tool calls. The next `session.prompt()` re... | | `CohereModelProvider` | `value` | Public export. | | `CohereProviderOptions` | `type` | Public export. | | `combineSubmissionTelemetrySinks` | `value` | Fan one event out to several sinks. | | `Command` | `type` | Public export. | | `CommandEnvValue` | `type` | Public export. | | `CommandPolicy` | `type` | Public export. | | `CommandToolInput` | `type` | Public export. | | `CommandToolOptions` | `type` | Public export. | | `CompactionOptions` | `type` | Public export. | | `CompactionResult` | `type` | Public export. | | `configureDispatchRuntime` | `value` | Configure the ambient dispatch queue used by `dispatch`. | | `configureJobInvocationRuntime` | `value` | Public export. | | `connectFabricVoice` | `value` | Public export. | | `connectFabricWs` | `value` | Public export. | | `connectMcpServer` | `value` | Public export. | | `consoleTelemetryExporter` | `value` | Telemetry exporter that writes spans through the SDK logger (default `console`-backed). Useful for local development and as a fallback when no OpenTelemetry collector is wired up. Use `openTelemetryExporter` or `langfuseExporter` for production. | | `ContextBudget` | `type` | Public export. | | `ContextBudgetOptions` | `type` | Public export. | | `CONVERSATION_STREAM_DEFAULT_READ_LIMIT` | `value` | Public export. | | `CONVERSATION_STREAM_MAX_READ_LIMIT` | `value` | Public export. | | `conversationKey` | `value` | Public export. | | `ConversationProducerClaim` | `type` | Public export. | | `ConversationProjector` | `value` | Public export. | | `ConversationStreamAppendInput` | `type` | Public export. | | `ConversationStreamBatch` | `type` | Public export. | | `ConversationStreamIdentity` | `type` | Public export. | | `ConversationStreamMeta` | `type` | Public export. | | `conversationStreamPath` | `value` | Stream path for a session's conversation projection. | | `ConversationStreamReadResult` | `type` | Public export. | | `ConversationStreamRecord` | `type` | Append-only conversation stream — the durable, offset-addressable projection of a session's active path (v2 migration, phase A4). The SessionEntry DAG remains the single source of truth for model context; this stream exists so clients can read a conversation with offsets (catch-up + live tail), across processes, without count-based replay. The projection appends one record per active-path entry, and — because the DAG can branch (fork/replay/checkpoint-restore) while a stream cannot — an explicit `truncated` record whenever the active path rewinds, paired with a producer-epoch bump so stale... | | `ConversationStreamStore` | `type` | Durable append-only conversation stream storage. **Batch atomicity is a hard contract requirement**: every record in an `append` must be persisted together under one offset, all-or-nothing. First-party adapters satisfy this by serializing the batch into a single row/document write; an adapter that splits records across non-atomic writes violates the contract. **Producer fencing**: `acquireProducer` bumps the producer epoch; appends carrying a stale epoch are rejected. The (path, producerId, epoch, sequence) uniqueness makes redelivered appends idempotent — a retried append with the same coo... | | `ConversationStreamStoreError` | `value` | Public export. | | `CostAttribution` | `type` | Attribution dimensions for a single cost observation. All fields are optional so callers can tag as much or as little metadata as they have. | | `CostAttributionRow` | `type` | A single row of aggregated cost attribution data. | | `CostBudgetStore` | `type` | Async store for cross-process spend aggregation. Pair with `CostLimit.scopeKey` + `CostLimit.perScope` to enforce a budget that survives process restarts — "tenant:acme spends ≤ $50 today" or "company-wide ≤ $100 this hour". Built-in implementations: - `inMemoryCostBudgetStore()` — process-local; default. - `@fabric-harness/node` exposes `postgresCostBudgetStore({ pool })`. | | `CostBudgetTracker` | `value` | Tracks cumulative session spend. Cheap to construct; one per session. | | `CostLimit` | `type` | Public export. | | `CostLimitContext` | `type` | Public export. | | `CostLimitExceededError` | `value` | Public export. | | `createActivateSkillTool` | `value` | Public export. | | `createAgent` | `value` | Define a persistent, URL-addressable agent. Files in `.fabricharness/agents/` default-export `createAgent(...)`; the runtime resolves a fresh config per interaction and keeps sessions across direct prompts and dispatched inputs. | | `createAttachmentRef` | `value` | Build an `AttachmentRef` for the given bytes, computing the SHA-256 digest via WebCrypto (`crypto.subtle`) so the SDK stays runtime-agnostic. | | `createBuiltinTools` | `value` | Public export. | | `createCommandTools` | `value` | Public export. | | `createConsoleLogger` | `value` | Build a Console-backed logger with an explicit level. Useful for tests that want to capture or silence SDK output without touching globals. | | `CreatedAgent` | `type` | A persistent, addressable agent created with `createAgent`. Distinct from a finite `agent({ run })` job: it has no `run` — the initializer returns configuration, and the runtime maintains sessions across interactions. | | `createDirectAgentSubmissionInput` | `value` | Mint a direct-prompt submission input with a fresh submission id. | | `createDispatchAgentSubmissionInput` | `value` | Map a `DispatchInput` onto the persisted submission input shape. | | `createFabricContext` | `value` | Public export. | | `createFabricFs` | `value` | Adapt a sandbox into the public filesystem convenience surface. | | `createFileTools` | `value` | Public export. | | `createMcpAuthorizationCodeAuth` | `value` | Authorization-code + PKCE provider; the MCP SDK refreshes stored tokens automatically. | | `createMcpClientCredentialsAuth` | `value` | OAuth client-credentials provider with MCP SDK token refresh handling. | | `createMcpTools` | `value` | Public export. | | `CreateMcpToolsOptions` | `type` | Public export. | | `createObservabilityObserver` | `value` | Create a fail-open event observer suitable for Braintrust, Sentry, Jetty, or a custom sink. | | `createObservabilityRecord` | `value` | Convert a Fabric event into a vendor-neutral, low-cardinality record. | | `createOperationalMetricsCollector` | `value` | Low-cardinality operational metrics collector suitable for OTel/Prometheus bridging. | | `createRemoteSandboxEnv` | `value` | Wrap a provider-owned remote sandbox client in Fabric's SandboxEnv contract. Provider credentials and SDK objects remain outside model context/history; Fabric only sees the narrow file/shell/snapshot API exposed here. | | `createResultTools` | `value` | Produce the per-call `finish` and `give_up` tool pair for a given ResultValidator. - `finish`'s parameters are a generic JSON Schema object because we can't derive a precise schema from `ResultValidator`. The validator's `safeParse` handles actual validation. - First successful `finish` (or `give_up`) call wins. Subsequent calls return an error tool result rather than throwing, to keep the conversation transcript natural. | | `createSandboxEnv` | `value` | Public export. | | `createScopedSandboxEnv` | `value` | Return a view of a sandbox with a narrower default cwd. Relative file paths and shell cwd values are resolved from this scoped cwd while the underlying sandbox still enforces its workspace boundary. | | `createSearchTool` | `value` | Exposes a `Retriever` to the model as a `search` tool. The tool is read-only; wrap it with a governance decorator to stamp lineage or route approvals. | | `createSessionSubmissionExecutor` | `value` | Provider-neutral durable submission executor for hosts that can open a Fabric session themselves. Node, Durable Objects, and custom runtimes share the same recovery and conservative interrupted-tool settlement behavior. | | `createStdioMcpClient` | `value` | Public export. | | `createSubmissionRunner` | `value` | Public export. | | `createUnifiedInMemoryStore` | `value` | Factory that returns a fresh `UnifiedInMemoryStore`. The returned object can be passed as `store`, `streamChunkStore`, and `runStore` simultaneously. | | `createVirtualSandboxEnv` | `value` | Public export. | | `CredentialMissingStrategy` | `type` | Public export. | | `currentJobInvocation` | `value` | Public export. | | `currentSubmissionContext` | `value` | The submission owning the current execution, or `undefined` outside one. | | `DeepgramSttProvider` | `value` | Public export. | | `DeepgramSttProviderOptions` | `type` | Public export. | | `DEFAULT_HEADLESS_PREAMBLE` | `value` | Public export. | | `defaultAgentProfile` | `value` | Public export. | | `defaultLoopRuntime` | `value` | Public export. | | `defaultModelProvider` | `value` | Public export. | | `defaultSessionStore` | `value` | Public export. | | `defineAction` | `value` | Define an action — the harness-context counterpart to `defineTool`. A tool is a leaf capability the model calls; an action holds the harness (`context.init`) and can prompt, spawn sessions, and compose other work. Input/output use the harness `schema` builders and are validated on every `runAction` call; outputs must be JSON-serializable. | | `defineAgent` | `value` | Canonical v2 name for `createAgent`: persistent, URL-addressable agents are *defined*, and the runtime instantiates them per addressed id. `createAgent` remains supported as an alias. | | `defineAgentProfile` | `value` | Public export. | | `defineChannel` | `value` | Validates and brands a channel's routes. | | `defineCommand` | `value` | Public export. | | `DefinedAgent` | `type` | Public export. | | `defineJob` | `value` | Defaults-injecting finite-job builder exported from the bare SDK entry point. | | `defineTool` | `value` | Public export. | | `defineWebhookSubscription` | `value` | Helper that returns the definition unchanged. Useful for type inference and to keep agent files declarative. See the package declarations for an example. | | `DeletionCompletionRecord` | `type` | Public export. | | `DeliveredAttachment` | `type` | One attachment on a `kind: 'user'` message. Today the only supported attachment is an image, carried either inline (`data`, base64) or as a durable content-addressed reference (`ref`) once an attachment store is configured — admission materializes inline bytes into refs. An attachment must carry `data` or `ref` (or both, transiently during materialization). | | `DeliveredAttachmentRef` | `type` | Durable reference to attachment bytes in an attachment store. | | `DeliveredMessage` | `type` | DeliveredMessage — the single unified input shape for everything that enters a persistent agent's session: direct HTTP prompts, dispatch, channels/webhooks, Databricks events, SDK clients, and tests. `kind: 'user'` is a direct user talking to the assistant (1:1 chat surface), optionally carrying attachments. `kind: 'signal'` models everything beyond that direct exchange — a Slack thread or a Lakeflow job event is activity the agent observes, not the assistant's own user speaking. Sender identity and structured metadata go in `attributes`; the message itself in `body`. Signals render into mo... | | `deliveredSignalToEntryData` | `value` | Map a signal-kind message onto the persisted `signal` entry's data shape. | | `deriveCompactionDefaults` | `value` | Compute model-aware compaction defaults. Reserve is capped at the model's max output because reserving more than the model can emit in one turn wastes context; the preserved tail stays flat because recent-context fidelity depends on the active work, not on the model's total window size. | | `dispatch` | `value` | Public export. | | `DispatchInput` | `type` | Internal enqueued form, carrying correlation + isolation metadata. | | `DispatchProcessor` | `type` | Consumes enqueued dispatches and applies them to an instance session. | | `DispatchQueue` | `type` | Admission queue for dispatches. The default is in-process; durable backends implement the same shape. | | `DispatchReceipt` | `type` | Acceptance confirmation for an enqueued dispatch. | | `DockerSandboxEnv` | `value` | Public export. | | `DockerSandboxOptions` | `type` | Public export. | | `DURABILITY_DEFAULT_MAX_ATTEMPTS` | `value` | Default maximum total attempts before terminalization. | | `DURABILITY_DEFAULT_TIMEOUT_MS` | `value` | Default submission timeout in milliseconds (one hour). | | `DurableSessionRuntime` | `type` | Structural delegate for durable session execution. When `init()` is given a `sessionRuntime` factory that produces one of these, the SDK's session calls (`prompt`, `task`, `shell`, `checkpoint.*`) are routed through the runtime instead of executing inline. This is the seam for Temporal workflows, external orchestration runtimes, or test fakes. `mount`, `history`, `artifact`, and `compact` remain SDK-local concerns and are not delegated — they operate against the local session store. | | `DurableSessionRuntimeFactory` | `type` | Public export. | | `editFileTool` | `value` | Public export. | | `EditInput` | `type` | Public export. | | `ElevenLabsTtsProvider` | `value` | Public export. | | `ElevenLabsTtsProviderOptions` | `type` | Public export. | | `EmbeddingProvider` | `type` | Embeddings seam. Feeds self-managed-embedding vector indexes (embed the query → query vector) and any bring-your-own retrieval pipeline. Returns one vector per input text, order-preserving. | | `emitOpenTelemetrySpan` | `value` | Public export. | | `emitSubmissionTelemetry` | `value` | Deliver an event to a sink, swallowing (and reporting) sink failures. | | `EmptySandboxEnv` | `value` | Public export. | | `enqueueDispatch` | `value` | Validate + normalize a named request and enqueue it, generating the dispatch id. | | `entryToTelemetrySpan` | `value` | Public export. | | `environmentSecretProvider` | `value` | Runtime-only environment provider with optional prefix and explicit allowlist. | | `EnvironmentSecretProviderOptions` | `type` | Public export. | | `estimateCostUsd` | `value` | Estimate USD cost for a single model call. Returns `0` when no row matches `modelRef` — callers should treat 0 as "unknown" and not overwrite an existing `costUsd` from the provider. | | `estimateModelMessagesTokens` | `value` | Public export. | | `estimateSessionEntriesTokens` | `value` | Public export. | | `estimateTextTokens` | `value` | Public export. | | `evaluateCommandPolicy` | `value` | Public export. | | `evaluateContextBudget` | `value` | Public export. | | `evaluateNetworkPolicy` | `value` | Evaluate a URL or `Request` against the configured network policy. Returns `{ allowed: true }` when the request is permitted, otherwise a denial with the reason and matched pattern. | | `evaluateOperationalSlos` | `value` | Public export. | | `evaluateToolCallPolicy` | `value` | Public export. | | `eventToTelemetrySpan` | `value` | Public export. | | `ExistsInput` | `type` | Public export. | | `existsTool` | `value` | Public export. | | `extractResultValue` | `value` | Public export. | | `FABRIC_OPERATIONAL_METRICS` | `value` | Public export. | | `FabricActor` | `type` | Public export. | | `FabricAgent` | `type` | Public export. | | `FabricContext` | `type` | Public export. | | `FabricError` | `value` | Public export. | | `FabricErrorCode` | `type` | Public export. | | `FabricErrorOptions` | `type` | Public export. | | `FabricEvent` | `type` | Public export. | | `FabricEventCallback` | `type` | Public export. | | `FabricEventType` | `type` | Public export. | | `FabricFs` | `type` | Out-of-band filesystem surface for a session sandbox. These operations do not write to conversation history and are intended for host-side plumbing: staging files, collecting artifacts, and preparing scratch space. If the model should reason about a file, prompt it to use the normal read/write/edit tools instead. | | `FabricObservabilityRecord` | `type` | Public export. | | `FabricPrincipal` | `type` | The governed identity a piece of work runs as (v2). Distinct from `ActorIdentity` (who asked): the principal is what the platform's access control enforces — a human user, a machine service principal, or a hosted app's own identity. `ucPrincipal` carries the catalog-governance principal name when the platform has one (e.g. Unity Catalog). | | `FabricRuntime` | `type` | Execution runtime selection. - `inline` (default): single-process execution. Uses the configured `SessionStore` (in-memory by default) for history, artifacts, approvals. - `stateless`: explicit headless / ephemeral mode. No session store, no artifact persistence, no approval waiting. Each invocation is independent. Use for high-volume webhook agents and edge runtimes where state would just be discarded anyway. In production (`FABRIC_ENV=production` or `NODE_ENV=production`) this mode must be selected explicitly — `inline` without an explicit store will warn or fail depending on `FABRIC_ALLO... | | `FabricSession` | `type` | Public export. | | `FallbackModelProvider` | `value` | Public export. | | `FallbackModelProviderOptions` | `type` | Public export. | | `FileStat` | `type` | Public export. | | `FilesystemEntry` | `type` | Public export. | | `FilesystemPolicy` | `type` | Public export. | | `FilesystemSource` | `type` | A read-only content source that can be mounted into a sandbox at sandbox-creation time. The agent then has built-in `read`, `glob`, and `grep` tools available over the mounted content — no retrieval pipeline, no embeddings, no vector store required. Sources are intentionally minimal: they yield (path, content) pairs. Implementations decide how to enumerate (eager vs lazy is up to the source author) — the mount step pulls the full set into the sandbox. | | `findSubmissionInputIndex` | `value` | Index of the (last) `user_prompt` entry carrying the submission id, or -1. | | `findTrailingDanglingToolCalls` | `value` | Find trailing `tool_call` entries on the active path that were never settled — no matching `tool_result` (paired by `toolCallId`, falling back to tool name) and no subsequent `error` entry for the same tool. A dangling call means a model turn died (crash/abort) between recording the call and recording its outcome. Left in place it produces an assistant `tool_use` with no `tool_result` on resume, which providers reject — the repair path appends synthetic interrupted outcomes for exactly the entries returned here. Conservative by construction: only the window after the last turn boundary (use... | | `findTrailingUnfinishedTasks` | `value` | Trailing `task_start` entries in the same window with no matching `task_end` — a subtask that was in flight when the turn died. These do not corrupt model context (task entries are bookkeeping), but settling them keeps UI/audit state coherent. | | `formatSchemaIssues` | `value` | Public export. | | `formatStreamOffset` | `value` | Public export. | | `fumadocsSource` | `value` | Mount a local Fumadocs content directory as a knowledge base. Strips MDX frontmatter by default for cleaner agent context. For a published Fumadocs site, fetch its `llms.txt` / sitemap and pass the URLs to `httpFilesystemSource`. | | `GeminiModelProvider` | `value` | Public export. | | `GeminiProviderOptions` | `type` | Public export. | | `generateAffinityKey` | `value` | Generate a deterministic `aff_\u003cULID\u003e` affinity key from an `(agentId, sessionId)` pair. The same pair always produces the same key, which is stable across restarts. Different pairs produce different keys with overwhelming probability. | | `generateWithRuntime` | `value` | Public export. | | `getAgentDefinition` | `value` | Public export. | | `getCreatedAgent` | `value` | Return the `CreatedAgent` carried by a value, or `undefined`. | | `getLogger` | `value` | Get the currently configured logger. | | `getVirtualSandbox` | `value` | One-liner helper for the most common pattern: mount a single read-only source into a virtual sandbox. Equivalent to: See the package declarations for an example. Used for support agents, runbook lookup, FAQ assistants — anywhere a small Markdown corpus needs to be searchable via the agent's built-in `grep`/`glob`/`read` tools. See the package declarations for an example. | | `GlobInput` | `type` | Public export. | | `globTool` | `value` | Public export. | | `GrepInput` | `type` | Public export. | | `GrepMatch` | `type` | Public export. | | `grepTool` | `value` | Public export. | | `hasSubmissionSettledEntry` | `value` | True when the path carries a canonical `submission_settled` entry for the id. | | `hexToBytes` | `value` | Public export. | | `hmacSha256` | `value` | Public export. | | `httpFilesystemSource` | `value` | Fetch a list of URLs and mount each response body as a file. Useful for pulling a small published docs set into the sandbox so the built-in `read`/`grep`/`glob` tools can search it like local files. | | `HttpResource` | `type` | Public export. | | `init` | `value` | Public export. | | `initializePersistentAgent` | `value` | Resolve a persistent agent's config for an instance and build a `FabricAgent`. Runtime-specific resources (session store, workspace roles/skills, loop runtime) are layered by the host that calls this. | | `inMemoryApprovalNotificationStore` | `value` | Process-local atomic delivery state for development and single-process hosts. | | `InMemoryAttachmentStore` | `value` | In-memory attachment store (dev / `runtime: 'stateless'` / tests). | | `InMemoryConversationStreamStore` | `value` | In-memory conversation stream store (dev / `runtime: 'stateless'` / tests). | | `inMemoryCostBudgetStore` | `value` | Process-local cost budget store. Default when `store` is not provided. | | `InMemoryDispatchQueue` | `value` | In-process dispatch queue: microtask-drained, concurrent across sessions, serialized within a single session. Suitable for the `inline`/`stateless` runtimes and dev. Durable delivery (surviving restarts) is provided by the Temporal-backed queue, which implements this same interface. | | `inMemorySessionMemory` | `value` | Process-local in-memory implementation. Default when `init({ memory })` is not configured — pair with Postgres for durability across restarts. | | `InMemorySessionStore` | `value` | Public export. | | `inMemorySource` | `value` | Build a source from an in-memory map of `path -> content`. Useful for tests, fixtures, and small static knowledge bases bundled into the agent module itself. | | `InMemoryStreamChunkStore` | `value` | Public export. | | `InMemorySubmissionStore` | `value` | Public export. | | `InterruptedToolCallRef` | `type` | A tool call settled with an explicit interrupted-outcome marker at terminalization. | | `InvalidDeliveredMessageError` | `value` | Thrown by `parseDeliveredMessage` on malformed input. | | `invoke` | `value` | Public export. | | `isActionDefinition` | `value` | Public export. | | `isContextOverflowError` | `value` | Public export. | | `isCreatedAgent` | `value` | Whether a value is a `CreatedAgent`. | | `isDeliveredMessageShape` | `value` | True when a raw value already looks like a `DeliveredMessage` (has a valid `kind`). | | `isEvent` | `value` | Type guard: narrow an `AgentEvent` to a specific variant. See the package declarations for an example. | | `isFabricError` | `value` | Public export. | | `isInMemoryStore` | `value` | Returns true when `store` is the in-memory default (no `appendEntry` persistence beyond memory). Used by stateless mode to skip writes. | | `isStatelessRuntime` | `value` | Public export. | | `isSubmissionPayload` | `value` | Validate that a parsed JSON payload matches the expected submission shape. Used after deserializing a persisted payload to verify the object is a well-formed `AgentSubmissionInput` that is consistent with the stored submission metadata. Both dispatch and direct payloads carry the same `message: DeliveredMessage` field — validated identically here regardless of transport `kind`. | | `isValidAffinityKey` | `value` | Public export. | | `job` | `value` | Defaults-injecting agent builder. Exported as `agent` from the bare `@fabric-harness/sdk` entry point. Wraps `init()` so the headless defaults are applied unless the caller overrides them: - `runtime: 'stateless'` - `sandbox: 'virtual'` - `loopRuntime: pi-agent-core` - `compaction: { enabled: true }` If `runtime: 'temporal'` is selected from this builder, a one-time `console.warn` is emitted because auto-compaction is non-deterministic across Temporal replay. Use `@fabric-harness/sdk/strict` for Temporal. | | `JobContext` | `type` | Runtime-ready context for finite jobs. The default session is initialized lazily. | | `JobDefinition` | `type` | Public export. | | `JobInvocationContext` | `type` | Public export. | | `JobInvocationOptions` | `type` | Public export. | | `JobInvocationReceipt` | `type` | Public export. | | `JobInvocationRuntime` | `type` | Public export. | | `JobMiddleware` | `type` | Public export. | | `JournalCallbacks` | `type` | Public export. | | `jsonDeepEqual` | `value` | Structural equality over JSON values (objects compared key-order-insensitively). | | `JsonObject` | `type` | Public export. | | `JsonPrimitive` | `type` | Public export. | | `JsonSchemaObject` | `type` | Public export. | | `JsonValue` | `type` | Public export. | | `LangfuseClientLike` | `type` | Optional Langfuse exporter. Adapts Fabric's `TelemetrySpan` shape to Langfuse's tracing API. The Langfuse client is provided by the caller — we don't take a hard dependency. Install peer dep: See the package declarations for an example. Usage: See the package declarations for an example. | | `langfuseExporter` | `value` | Public export. | | `LangfuseExporterOptions` | `type` | Public export. | | `LEASE_DURATION_MS` | `value` | Default lease duration for submission ownership in milliseconds (30 seconds). | | `listModelPrices` | `value` | All currently-registered rows (newest-last). Returns a copy. | | `listSandboxRefDecoders` | `value` | Returns the list of currently registered providers. | | `localDirectorySource` | `value` | Read a host directory recursively as a read-only source. `include` is called for each candidate file path (relative to `hostPath`). Return `false` to skip. Defaults to including everything. | | `LocalSandboxEnv` | `value` | Public export. | | `LocalSandboxOptions` | `type` | Public export. | | `Logger` | `type` | Minimal logger seam used by the SDK for non-event diagnostic output (warnings, deprecation notices, telemetry fallbacks). All `console.*` inside the SDK should route through `getLogger()` so ops teams can redirect or silence messages in production. The default logger writes to `console` and respects `FABRIC_HARNESS_LOG_LEVEL=debug\|info\|warn\|error\|silent` (default `warn`). | | `LogLevel` | `type` | Public export. | | `lookupModelPrice` | `value` | Look up the most recently-registered row matching `modelRef`. `modelRef` can be: - `'provider/model'` (preferred, e.g. `'openai/gpt-4o'`) - `'model'` alone (e.g. `'gpt-4o'`) — first row whose model matches wins Provider matching is case-insensitive. Model matching is exact. | | `materializeMessageAttachments` | `value` | Materialize a message's inline attachments into durable refs: decode the base64 data, store the bytes under `scope`, and return a NEW message whose attachments carry `{ type, mimeType, filename?, ref }` and no `data`. Deterministic by construction — attachment ids are `${idPrefix}_${index}` and digests derive from content — so an exact redelivery of the same message produces an identical materialized payload (admission idempotency). Messages without inline attachments are returned unchanged (same reference). | | `MAX_ATTACHMENT_DATA_LENGTH` | `value` | Maximum accepted base64 length for a single inline attachment. | | `McpAuthorizationCodeOptions` | `type` | Public export. | | `McpAuthorizationCodeState` | `type` | Public export. | | `McpClientCredentialsOptions` | `type` | Public export. | | `McpClientLike` | `type` | Public export. | | `McpServerConnection` | `type` | Public export. | | `McpServerOptions` | `type` | Public export. | | `McpToolDescriptor` | `type` | Public export. | | `McpTransport` | `type` | Public export. | | `memorySandboxOwnershipLeaseStore` | `value` | Process-local lease store for tests and single-replica durable workers. | | `messageHasDataAttachments` | `value` | True when the message carries at least one inline (base64) attachment. | | `mintlifySource` | `value` | Mount a checked-out Mintlify content directory. For a hosted Mintlify MCP server, use `connectMcpServer('mintlify', { url, transport: 'streamable-http' })` instead. | | `MissingInputStrategy` | `type` | Public export. | | `MkdirInput` | `type` | Public export. | | `mkdirTool` | `value` | Public export. | | `ModelAttemptEvent` | `type` | Public export. | | `ModelConfig` | `type` | Public export. | | `ModelMessage` | `type` | Public export. | | `ModelMessageRole` | `type` | Public export. | | `ModelMetadata` | `type` | Public export. | | `ModelPriceRow` | `type` | Static USD price table for model providers. Used to populate `ModelUsage.costUsd` on responses that don't include billing info from the provider directly (most providers — only `pi-loop-runtime` and a handful of gateways report cost). Prices are stamped with `effectiveAt`. The table is best-effort: real billing reconciliation should use vendor invoices. Override or extend at runtime with `registerModelPrices` for custom-rate contracts. | | `ModelPricingUsage` | `type` | Public export. | | `ModelProvider` | `type` | Public export. | | `ModelProviderFactory` | `type` | Resolves a parsed `provider/model-id` ref into a concrete provider. Registered factories let out-of-core packages (e.g. | | `ModelRequest` | `type` | Public export. | | `ModelResponse` | `type` | Public export. | | `ModelRuntimeOptions` | `type` | Public export. | | `ModelStreamChunk` | `type` | Public export. | | `ModelToolCall` | `type` | Public export. | | `ModelToolSchema` | `type` | Public export. | | `ModelUsage` | `type` | Public export. | | `MountedSource` | `type` | Public export. | | `MountResult` | `type` | Public export. | | `NamedAgentDispatchRequest` | `type` | A dispatch request that names its target agent. | | `NamedJobInvocation` | `type` | Public export. | | `NativeLoopRuntime` | `value` | Public export. | | `negotiateSandboxContinuity` | `value` | Report the continuity operations that a backend or serialized ref can support. | | `NetworkEnforcementLayer` | `type` | Public export. | | `NetworkEnforcementRequirement` | `type` | Public export. | | `NetworkPolicy` | `type` | Public export. | | `noopSessionStore` | `value` | Public export. | | `NoopSessionStore` | `value` | No-op session store for `runtime: 'stateless'` mode. All writes are discarded; reads always return the empty initial session. Use this when the agent is intended as a pure request/response handler with no persistence (typical for high-volume webhooks and edge runtimes). Approvals and artifact retrieval are not supported — wiring those up requires a real store. Callers in stateless mode should not rely on artifact persistence or approval gating. | | `normalizeDeliveredMessage` | `value` | Normalize legacy inputs into a `DeliveredMessage`: - a string → a user message with that body - a value with a `kind` discriminator → validated as a DeliveredMessage - any other JSON value → a user message with the JSON-stringified body (matching the historical dispatch rendering, so behavior is unchanged for pre-DeliveredMessage callers) | | `ObservabilityCorrelation` | `type` | Public export. | | `ObservabilityObserverOptions` | `type` | Public export. | | `OpenAIChatCompletion` | `type` | Public export. | | `openAIChatCompletionToModelResponse` | `value` | Public export. | | `OpenAICompatibleModelProvider` | `value` | Public export. | | `OpenAICompatibleProviderOptions` | `type` | Public export. | | `OpenAIRealtimeVoiceProvider` | `value` | OpenAI Realtime voice provider. Connects via WebSocket; emits `audio_delta` / `text_delta` / `transcript` / `tool_call` / `response_done` events. No transitive dependency on `ws` — uses the global `WebSocket` available on Node 22+ and browsers. See the package declarations for an example. | | `OpenAIRealtimeVoiceProviderOptions` | `type` | Public export. | | `openTelemetryExporter` | `value` | Bridge Fabric's SDK-neutral TelemetrySpan into a real | | `OpenTelemetryExporterOptions` | `type` | Public export. | | `OperationalMetricsCollector` | `type` | Public export. | | `OperationalMetricsSnapshot` | `type` | Public export. | | `OperationalSloEvaluation` | `type` | Public export. | | `OperationalSloTargets` | `type` | Public export. | | `parseConversationKey` | `value` | Public export. | | `ParsedConversationKey` | `type` | Public export. | | `parseDeliveredMessage` | `value` | Validate a raw value as a `DeliveredMessage`. Shared by dispatch admission and the direct HTTP route so every transport produces the same structured error on bad input. | | `ParsedModelRef` | `type` | Public export. | | `ParsedScopeKey` | `type` | Public export. | | `parseModelRef` | `value` | Public export. | | `parsePersistentSessionId` | `value` | Inverse of `persistentStoreSessionId`: decode a store session id back into `{ agent, instanceId, session }`, or `undefined` if it is not a persistent-instance key. Useful for admin surfaces that list raw session ids. | | `parseRetryAfterMs` | `value` | Parse a `Retry-After` header value (RFC 7231) into milliseconds. Accepts both delta-seconds and HTTP-date forms. Returns undefined when the value is missing or unparseable. | | `parseScopeKey` | `value` | Parse a scope key into its structured components. Recognized patterns: tenant:<id>:day:YYYY-MM-DD tenant:<id>:hour:YYYY-MM-DDTHH:00Z tenant:<id>:month:YYYY-MM agent:<id>:day:YYYY-MM-DD agent:<id>:hour:YYYY-MM-DDTHH:00Z agent:<id>:month:YYYY-MM user:<id>:day:YYYY-MM-DD user:<id>:hour:YYYY-MM-DDTHH:00Z user:<id>:month:YYYY-MM Unknown prefixes fall back to `kind: 'custom'` with `raw: scopeKey`. Returns `undefined` for empty strings. | | `parseStreamOffset` | `value` | Public export. | | `persistenceAdapter` | `value` | Create an in-memory `PersistenceAdapter` backed by a fresh `InMemorySessionStore`. This is the default when no persistence is configured. The returned adapter's `connect()` always returns the same store instance. | | `PersistenceAdapter` | `type` | A persistence adapter abstracts over storage backends (SQLite, Postgres, file, etc.). The SDK only requires `connect() → SessionStore`. Optional methods `connectRunStore` and `connectRunRegistry` are used by workflow backends (Temporal, Cloudflare). The optional `migrate()` hook is called once at startup to ensure schema exists, and `close()` releases resources. | | `PersistenceBundle` | `type` | Complete persistence surface consumed by a Fabric host. | | `PersistenceDeleteResult` | `type` | Public export. | | `PersistenceHealth` | `type` | Public export. | | `PersistentAgentConfig` | `type` | Runtime configuration returned by a `createAgent` initializer. Mirrors the agent-level slice of `AgentInit`; `instructions` becomes the session system prompt (a role), and `subagents` map to named roles. | | `PersistentAgentContext` | `type` | Per-interaction context passed to a `createAgent` initializer. `id` is the URL `<id>` of the addressed instance (or the dispatch target id); `env` is the platform environment supplied by the runtime. | | `persistentConfigToAgentInit` | `value` | Translate a `PersistentAgentConfig` into an `AgentInit`. | | `PersistentSessionIdentity` | `type` | Decoded identity of a persistent instance session store key. | | `persistentStoreSessionId` | `value` | Store-session key for a persistent instance's named session. Collapses the `(agentName, instanceId, sessionName)` identity onto Fabric's single-string `sessionId`, keeping persistent sessions inside the existing session stores. | | `PipelineVoiceProvider` | `value` | Public export. | | `PipelineVoiceProviderOptions` | `type` | Public export. | | `policiedFetch` | `value` | Public export. | | `PoliciedFetchOptions` | `type` | Wrap a `fetch`-like function with `CapabilityPolicy` enforcement. Tools and connectors that make outbound HTTP should accept a custom `fetch` and pass the result of `policiedFetch(fetch, policy)`. Throws a `FabricError` (`POLICY_DENIED`) when a request violates the policy; never sends a forbidden request. | | `PolicyDecision` | `type` | Public export. | | `PromptOptions` | `type` | Public export. | | `PromptRunInput` | `type` | Public export. | | `PromptRunResult` | `type` | Public export. | | `ProviderHttpError` | `value` | Public export. | | `ProvidersConfig` | `type` | Public export. | | `ProviderSettings` | `type` | Public export. | | `pruneSnapshots` | `value` | Public export. | | `RateLimiter` | `type` | Public export. | | `RateLimiterAcquireOptions` | `type` | Process-local rate limiter for outbound provider calls. Use to prevent a fleet of agents from stampeding a single API key when the host runs many sessions in parallel. The default token-bucket impl is in-memory; Redis-backed implementations are a v1.8+ topic. fabric-harness ships this as a generic primitive — the same limiter can be reused for outbound HTTP calls inside connectors, webhook fan-out, or anywhere else throttling is useful. | | `ReaddirInput` | `type` | Public export. | | `readdirTool` | `value` | Public export. | | `ReadFileBufferInput` | `type` | Public export. | | `readFileBufferTool` | `value` | Public export. | | `ReadFileInput` | `type` | Public export. | | `readFileTool` | `value` | Public export. | | `readJsonBody` | `value` | Reads the body once and returns the raw bytes, the decoded text, and the parsed JSON together — so a channel can HMAC-verify the exact bytes and use the JSON without re-reading the (already consumed) stream. Returns undefined only when the body exceeds `limitBytes`. | | `readRequestBody` | `value` | Reads the full request body as bytes, or returns undefined if it exceeds `limitBytes`. NOTE: this consumes the request stream (single read). Signature-verifying channels need the *exact* bytes for HMAC and the parsed JSON afterward — don't call `request.json()` as well. Use `readJsonBody` to get both from one read. | | `ReconstructedPartialAssistantMessage` | `type` | Public export. | | `reconstructInterruptedStream` | `value` | Public export. | | `redactError` | `value` | Public export. | | `RedactionOptions` | `type` | Public export. | | `redactJson` | `value` | Public export. | | `redactText` | `value` | Public export. | | `registerCreatedAgentName` | `value` | Register a name for a `CreatedAgent` so `dispatch(agent, ...)` can resolve it. | | `registeredModelProviders` | `value` | Names of externally registered providers, for diagnostics. | | `registerJobName` | `value` | Public export. | | `registerModelPrices` | `value` | Add or override price rows. Later rows take precedence over earlier ones. | | `registerModelProvider` | `value` | Register a model provider resolvable via `FABRIC_MODEL=<name>/<model-id>`. Built-in providers always take precedence; registering a name a built-in already owns has no effect on routing. Idempotent by name (last registration wins). Call at module import time. | | `registerSandbox` | `value` | Register a sandbox in the in-process registry and return a portable ref. Subsequent calls for the same env return the same ref. | | `registerSandboxRefDecoder` | `value` | Register a decoder for `provider` so `attachSandbox(serialized)` can rehydrate a sandbox from another process. Typically called once at startup by the package that owns the provider integration (e.g. `@fabric-harness/connectors/e2b` registers the `e2b` provider). | | `RemoteSandboxApi` | `type` | Public export. | | `RemoteSandboxOptions` | `type` | Public export. | | `renderDeliveredMessage` | `value` | Render a delivered message to the prompt text form. User messages pass their body through verbatim; signals render as their XML envelope via the shared `renderSignalMessage` machinery. | | `RequestBody` | `type` | Public export. | | `resetDispatchRuntime` | `value` | Clear the ambient dispatch runtime (tests/teardown). | | `resetJobInvocationRuntime` | `value` | Public export. | | `resetModelPricesToBuiltins` | `value` | Reset the registry to the built-in seed (test/utility). | | `ResolvedModelProvider` | `type` | Public export. | | `resolveModelProvider` | `value` | Public export. | | `ResolveModelProviderOptions` | `type` | Public export. | | `resolveRuntimeMode` | `value` | Resolve the effective runtime mode for an `init()` call, applying production safety rules: - In production (`FABRIC_ENV=production` or `NODE_ENV=production`), choosing `stateless` is allowed but logged as an explicit choice. - `inline` (the default) without an explicit `SessionStore` falls back to in-memory storage. In production this emits a warning unless `FABRIC_ALLOW_EPHEMERAL_STATE=1` is set or runtime is explicitly `'stateless'`. - Unknown runtime values fall through to `'inline'` with a warning. | | `RESULT_END_DELIMITER` | `value` | Public export. | | `RESULT_START_DELIMITER` | `value` | Public export. | | `ResultExtractionOptions` | `type` | Public export. | | `ResultOutcome` | `type` | Public export. | | `ResultToolBundle` | `type` | Public export. | | `ResultUnavailableError` | `value` | Thrown when the LLM calls the `give_up` tool, indicating it cannot produce a result that conforms to the required schema. | | `ResultValidator` | `type` | Public export. | | `RetrievedChunk` | `type` | Generic, provider-agnostic retrieval seam. RAG is query-time, so it does NOT fit `FilesystemSource` (an eager full-dump mount) — a retriever resolves the top matches for a query on demand. Databricks Vector Search is the flagship implementation, but this is reusable for any vector store. | | `RetrieveOptions` | `type` | Public export. | | `Retriever` | `type` | Public export. | | `RmInput` | `type` | Public export. | | `rmTool` | `value` | Public export. | | `Role` | `type` | Public export. | | `runAction` | `value` | Validate input, run the action against `host`, validate + JSON-clone the output. The returned value is always safely serializable (a fresh JSON clone), so callers can persist or transmit it without sharing references. | | `RunEvent` | `type` | A workflow event with append-only identity enforced by (runId, eventIndex). | | `RunRegistry` | `type` | Minimal interface for a workflow run registry. Used by durable runtime backends to index and track run statuses. | | `RunStore` | `type` | Minimal interface for a workflow/event run store. Used by durable runtime backends (Temporal, Cloudflare, etc.) to persist run metadata and events. | | `RuntimeModeResolution` | `type` | Public export. | | `runWithJobInvocation` | `value` | Public export. | | `runWithSubmissionContext` | `value` | Run `fn` with `context` as the ambient submission correlation. | | `SandboxAdapterDescriptor` | `type` | Adapter expectations: - Every backend must expose the SandboxEnv contract above and map paths into a scoped workspace. - Secrets and provider credentials must stay in adapter-owned environment/config, not model context. - exec should enforce backend-specific command, network, and timeout policy before process launch. - snapshot/restore is optional because not all targets support filesystem or VM snapshots. - Future adapters should be added without changing session/runtime code. Planned backends: local, Docker, Azure Container Apps, Azure Container Instances, AKS, Databricks, E2B, Daytona, C... | | `SandboxBackend` | `type` | Public export. | | `SandboxCapabilities` | `type` | Public export. | | `SandboxContinuityCapabilities` | `type` | Public export. | | `SandboxContinuityMode` | `type` | Public export. | | `SandboxEnv` | `type` | Public export. | | `SandboxExecOptions` | `type` | Public export. | | `SandboxFactory` | `type` | Public export. | | `SandboxFactoryOptions` | `type` | Public export. | | `SandboxFork` | `type` | Public export. | | `SandboxOwnershipLeaseStore` | `type` | Public export. | | `SandboxOwnershipOptions` | `type` | Public export. | | `SandboxRef` | `type` | Public export. | | `SandboxRefDecoder` | `type` | Decoder for a `SerializedSandboxRef.provider`. Returns a SandboxFactory that, when invoked, produces a SandboxEnv connected to the existing remote sandbox identified by `providerData`. Decoders SHOULD attach without owning the remote sandbox's lifecycle — the returned env's `cleanup()` should detach, not destroy. | | `SandboxSnapshot` | `type` | Public export. | | `sanitizeObservabilityData` | `value` | Public export. | | `sanitizePublicJson` | `value` | Public export. | | `sanitizePublicText` | `value` | Remove credentials and host filesystem locations from caller-visible text. | | `schema` | `value` | Public export. | | `Schema` | `type` | Public export. | | `SchemaIssue` | `type` | Public export. | | `SchemaValidationError` | `value` | Public export. | | `SearchToolInput` | `type` | Public export. | | `SearchToolOptions` | `type` | Public export. | | `SearchToolResult` | `type` | Public export. | | `secret` | `value` | Public export. | | `SecretProvider` | `type` | Public export. | | `SecretRef` | `type` | Public export. | | `SecretResolutionContext` | `type` | Public export. | | `secretResolver` | `value` | Adapt a provider to the existing `init({ resolveSecret })` callback. | | `SerializedFabricError` | `type` | Public export. | | `SerializedSandboxRef` | `type` | Cross-process / cross-machine sandbox reference. Created by `session.sandboxRef({ portable: true })` and re-attached via `attachSandbox(serialized)` in a separate process. Each `provider` string maps to a decoder registered via `registerSandboxRefDecoder()`. | | `serializeFabricError` | `value` | Convert any thrown value into the stable public/developer transport shape. | | `serializeSandboxRef` | `value` | Serialize an in-process `SandboxRef` into the cross-process form. Requires the underlying sandbox to implement `encodeRef()`. Throws `SANDBOX_UNAVAILABLE` if the backend is in-process-only. | | `SessionData` | `type` | Public export. | | `SessionEntry` | `type` | Public export. | | `SessionEntryType` | `type` | Public export. | | `SessionHistory` | `value` | Public export. | | `SessionMemory` | `type` | Public export. | | `SessionMemoryEntry` | `type` | Persistent key/value store for facts an agent should remember across sessions — borrower preferences, prior outcomes, learned task history. Distinct from `SessionEntry` (which is the audit log): memory is for *recall*, entries are for *audit*. Memory writes do NOT land in the session log, so they don't pollute prompt context unless the agent explicitly reads them. Tenancy: every operation accepts an optional `tenantId`. Two tenants with the same `key` get isolated values. `tenantId` defaults to the empty string for non-tenant deployments. | | `SessionMemoryFilter` | `type` | Public export. | | `SessionMemoryGetOptions` | `type` | Public export. | | `SessionMemorySetInput` | `type` | Public export. | | `SessionOptions` | `type` | Public export. | | `SessionStore` | `type` | Public export. | | `SessionSubmissionExecutorOptions` | `type` | Public export. | | `setLogger` | `value` | Replace the global SDK logger. Call once at startup before any `init()`. Pass a custom Logger to redirect to your structured logging system. | | `ShellOptions` | `type` | Public export. | | `shellQuote` | `value` | Public export. | | `ShellResult` | `type` | Public export. | | `Skill` | `type` | Public export. | | `SkillOptions` | `type` | Public export. | | `slackApprovalNotifier` | `value` | Slack incoming-webhook notifier. The webhook URL remains in host configuration, never event data. | | `SnapshotPruneOptions` | `type` | Public export. | | `SnapshotPruneResult` | `type` | Public export. | | `StatInput` | `type` | Public export. | | `statTool` | `value` | Public export. | | `StdioMcpClient` | `value` | Public export. | | `StdioMcpClientOptions` | `type` | Public export. | | `StoredAttachment` | `type` | Public export. | | `StreamChunkStore` | `type` | Public export. | | `StreamChunkWriter` | `value` | Public export. | | `StreamListenerRegistry` | `value` | Process-local listener registry shared by store implementations — registration, unsubscribe-and-prune, and error-swallowing notify. | | `SttEvent` | `type` | Public export. | | `SttProvider` | `type` | Public export. | | `SttSession` | `type` | Public export. | | `SttSessionOptions` | `type` | Public export. | | `SttSessionUsage` | `type` | Public export. | | `SubmissionAbortedError` | `value` | Public export. | | `SubmissionAdmissionBackend` | `type` | Storage callbacks for `admitSubmissionWithBackend`. Every callback runs inside the transaction the caller has already opened (or the backend's equivalent atomicity scope). Callbacks may return plain values (synchronous backends) or native `Promise`s — non-native thenables are not supported. | | `SubmissionAdmissionRow` | `type` | The minimal shape `admitSubmissionWithBackend` needs from a persisted submission row: the transport `kind` and persisted `payload` it compares against the incoming admission. `payload` may be the serialized JSON string or an already-deserialized object (e.g. a Postgres JSONB column). | | `SubmissionAttemptRef` | `type` | Public export. | | `SubmissionClaimRef` | `type` | Public export. | | `SubmissionContext` | `type` | Public export. | | `SubmissionDurability` | `type` | Public export. | | `SubmissionExecuteOptions` | `type` | Public export. | | `SubmissionExecutor` | `type` | How the runner touches sessions. `execute` applies the submission's input to the addressed instance session and resolves with the turn result; everything else is store-level and must not require a live agent. Contract requirements: - `execute` must be idempotent by submission id (a resumed attempt whose input entry already exists must not append it again). - `recordTerminal` settles the conversation to a deterministic rest state (unresolved trailing tool calls get explicit interrupted-outcome markers — NEVER re-executed) and appends a terminal advisory. - `appendSettlement` appends the cano... | | `SubmissionInsertRow` | `type` | The queued row that `admitSubmissionWithBackend` writes on first admission. | | `SubmissionInspection` | `type` | Coarse persisted-progress classification consumed by reconciliation. | | `SubmissionInterruptedError` | `value` | Public export. | | `SubmissionInterruption` | `type` | Public export. | | `SubmissionPayloadContext` | `type` | Context needed for submission payload validation. Implementations extract these fields from their storage-specific row/document type before calling `isSubmissionPayload`. | | `SubmissionRetryExhaustedError` | `value` | Public export. | | `SubmissionRunner` | `type` | Public export. | | `SubmissionRunnerOptions` | `type` | Public export. | | `submissionSessionKey` | `value` | Store-session FIFO key of a submission (re-exported convenience). | | `SubmissionSettledRecord` | `type` | Minimal canonical settlement record for a direct submission. The conversation-stream phase reuses this shape as the durable terminal record a reconnecting waiter observes. | | `SubmissionSettlement` | `type` | Public export. | | `submissionSettlementEntryId` | `value` | Deterministic canonical settlement entry id for a submission. | | `SubmissionSettlementObligation` | `type` | Public export. | | `submissionStoreSessionId` | `value` | The harness identity string (`agent:<name>:<id>:<session>`) targeted by a submission input. This is the `persistentStoreSessionId` of the addressed instance session and the per-session FIFO key of the store. | | `SubmissionTelemetryEvent` | `type` | Public export. | | `SubmissionTelemetrySink` | `type` | Public export. | | `SubmissionTimeoutError` | `value` | Public export. | | `TaskOptions` | `type` | Public export. | | `TelemetryExporter` | `type` | Public export. | | `TelemetrySpan` | `type` | Public export. | | `tenantCostLimit` | `value` | Sugar over `CostLimit.perScope + scopeKey + store` for the common "per-tenant ceiling per period" pattern. Pick one of `perDayUsd`, `perHourUsd`, or `perMonthUsd`; when multiple are set, the most restrictive (smallest absolute) wins. Scope key convention: `tenant:<id>:<period>` where `<period>` is `day:YYYY-MM-DD`, `hour:YYYY-MM-DDTHH:00Z`, or `month:YYYY-MM`. Reset semantics (rollover) are the host's job — call `store.reset(scopeKey)` from a scheduled task to clear the period total. See the package declarations for an example. | | `TenantCostLimit` | `type` | Public export. | | `ThinkingLevel` | `type` | Reasoning-effort input level, ordered from least to most thinking. Maps to each provider's native control (Workers AI / OpenAI `reasoning_effort`, Anthropic `thinking.budget_tokens`, Gemini `thinkingConfig`). `'off'` (the default when unset) requests no reasoning. Providers that don't support reasoning ignore the level — see `ModelMetadata.supportsReasoning`. | | `toFabricError` | `value` | Public export. | | `tokenBucketRateLimiter` | `value` | In-memory token-bucket rate limiter. Each key has its own bucket — keys are independent (waiting on one key doesn't block another). Buckets refill continuously at `tokensPerSecond`. | | `TokenBucketRateLimiterOptions` | `type` | Public export. | | `ToolCall` | `type` | Public export. | | `ToolCallResult` | `type` | Public export. | | `ToolContext` | `type` | Public export. | | `ToolDef` | `type` | Public export. | | `ToolEffect` | `type` | Public export. | | `ToolPolicy` | `type` | Public export. | | `toolsToModelSchemas` | `value` | Public export. | | `toOpenAIMessage` | `value` | Public export. | | `toOpenAITool` | `value` | Public export. | | `TtsProvider` | `type` | Public export. | | `TtsSynthesisOptions` | `type` | Public export. | | `TtsSynthesisUsage` | `type` | Public export. | | `TurnJournalState` | `type` | Public export. | | `UnifiedInMemoryStore` | `value` | A unified in-memory store that implements `SessionStore`, `StreamChunkStore`, and `RunStore`. Useful for testing and dev environments where a single object needs to be passed as `store`, `streamChunkStore`, and `runStore`. Backed by separate Maps for each concern so that stream chunks and run data do not pollute session state. | | `UnimplementedSandboxEnv` | `value` | Public export. | | `unregisterSandbox` | `value` | Mark a registered sandbox as dead so future attach attempts fail. Called from the owner session's cleanup path. | | `unregisterSandboxRefDecoder` | `value` | Test/internal: remove a decoder. | | `validateResult` | `value` | Public export. | | `VERCEL_AI_GATEWAY_BASE_URL` | `value` | Default base URL for Vercel AI Gateway's OpenAI-compatible Chat Completions endpoint. The gateway accepts the standard OpenAI request body and routes through to the configured provider; switching from OpenAI to the gateway is just a base-URL change. See https://vercel.com/docs/ai-gateway/sdks-and-apis/openai-compat | | `vercelAIGateway` | `value` | Vercel AI Gateway model provider. The gateway is an OpenAI-compatible HTTP endpoint that brokers between your agent and any of the major model providers (OpenAI, Anthropic, Google, xAI, Groq, etc.) with a single key, observability, caching, and spend controls. Use this provider on any deploy target — Node, Cloudflare Workers, Vercel — to route inference through the gateway. See the package declarations for an example. Returns an `OpenAICompatibleModelProvider` configured for the gateway — use it anywhere a `ModelProvider` is accepted. Compatible with fabric-harness's tool-calling, retries,... | | `VercelAIGatewayProviderOptions` | `type` | Public export. | | `verifyAttachmentBytes` | `value` | Verify that `bytes` match the ref's digest (and declared size), throwing `AttachmentStoreError('DIGEST_MISMATCH')` otherwise. Every store's `put` MUST run this check before persisting. | | `verifyHmacSha256` | `value` | Constant-time HMAC-SHA256 verification (via `crypto.subtle.verify`). | | `VertexAIModelProvider` | `value` | Public export. | | `VertexAIProviderOptions` | `type` | Public export. | | `VirtualSandboxEnv` | `value` | Virtual sandbox backend powered by [just-bash](https://github.com/vercel-labs/just-bash). Provides an in-memory filesystem and a bash subset (`grep`, `glob`, `cat`, `read`, `mkdir`, `rm`, `ls`, `echo`, etc.) without shelling out to the host. The backend is fast, cheap, safe, and high-concurrency. Selected automatically by the bare `@fabric-harness/sdk` import when the caller doesn't pass `sandbox`. Override with `'local'`, `'docker'`, a `SandboxFactory`, or a `SandboxEnv` when you need real shell access. | | `VoiceAudioFormat` | `type` | Bidirectional voice / audio streaming surface. fabric-harness ships an OpenAI Realtime implementation; bring-your-own-vendor for Anthropic / Gemini Live / on-prem TTS+ASR pipelines. Audio frames flow in raw bytes — the standard format is PCM 16-bit little-endian at 24kHz mono (OpenAI Realtime default). Telephony bridges (Twilio Media Streams μ-law 8kHz, etc.) resample at the edge. Tool execution is the *caller's* responsibility: a `tool_call` event surfaces, the host code runs the tool through whatever governance gates apply (approvals, cost caps, rate limits), then calls `submitToolResult(... | | `VoiceConnectOptions` | `type` | Public export. | | `VoiceEvent` | `type` | Events streamed from a `VoiceSession`. `audio_delta` carries raw audio bytes; `text_delta` and `transcript` carry text; `tool_call` and `response_done` mark structured boundaries; `error` is fatal. | | `VoiceProvider` | `type` | Public export. | | `VoiceSession` | `type` | Public export. | | `VoiceToolResultInput` | `type` | Public export. | | `VoiceWsClientEvent` | `type` | Public export. | | `VoiceWsClientHandle` | `type` | Public export. | | `VoiceWsClientOptions` | `type` | Lightweight WebSocket client for the `fh server` `WS /sessions/:id/voice` endpoint. Server-side bridge owns the provider connection and API keys; this client just streams audio + control messages over WS. Works in browsers and on Node 22+ (uses the global `WebSocket`). | | `webhookApprovalNotifier` | `value` | Public export. | | `WebhookSubscriptionContext` | `type` | Generic webhook subscription primitive — wakes an agent on inbound events from any external system (event bus, queue, scheduler, third-party SaaS webhook, your own application's domain events). fabric-harness consumes a JSON payload and dispatches to the user-provided handler. The host event system decides which payloads land here; fabric-harness has no opinion on event taxonomy or producer. | | `WebhookSubscriptionDefinition` | `type` | Public export. | | `withConversationProjection` | `value` | Wrap a `SessionStore` so active-path appends are mirrored into an append-only `ConversationStreamStore` projection (v2 A4). The SessionEntry DAG stays the single source of truth; the stream gives clients offset-based catch-up + live tail. Two rules keep them coherent: 1. **Idempotent by entryId** — a crash between the DAG write and the stream write is repaired on the next append: the projector diffs the stream tail against the active path and re-emits anything missing. 2. **Truncation is explicit** — the DAG can branch (fork/replay/ checkpoint-restore rewrite `leafId`); the stream cannot. W... | | `withFilesystemSources` | `value` | Public export. | | `WriteFileInput` | `type` | Public export. | | `writeFileTool` | `value` | Public export. | | `WsClientCommand` | `type` | Public export. | | `WsClientHandle` | `type` | Public export. | | `WsClientOptions` | `type` | Lightweight WebSocket client for the `fh server` `WS /sessions/:id/ws` endpoint. Works in browsers and on Node 22+ (uses the global `WebSocket`). Does NOT depend on the `ws` package — that's the server side's optional peer dep. | ### `@fabric-harness/sdk/strict` | Export | Kind | Summary | | --- | --- | --- | | `actionAsTool` | `value` | Expose an action to the model as a tool on an agent profile. The tool's JSON schema comes from the action's input schema; execution validates input/output exactly like `runAction`. | | `ActionContext` | `type` | Context passed to an action's `run`. Unlike a tool's `execute` (a leaf capability invoked by the model), an action receives the harness itself and can orchestrate: prompt models, spawn sessions, call other actions. | | `ActionDefinition` | `type` | A named, schema-validated unit of harness work created with `defineAction`. First-class in v2: registrable on agent profiles as a tool, callable from jobs and agents, evaluable via `fh test`, and deployable as a platform job task. | | `ActionError` | `value` | Public export. | | `ActionHost` | `type` | What an action runs against: the harness entry point (`init` can prompt models, spawn sessions, and call tools) plus the platform environment. Jobs, agents, and servers all satisfy this — it is the harness slice of `FabricContext`. | | `ActionOptions` | `type` | Public export. | | `ActorIdentity` | `type` | Public export. | | `admitSubmissionWithBackend` | `value` | Shared submission admission algorithm for row-oriented backends: dispatch-receipt check → insert-or-ignore → read-back → payload compare (idempotent replay vs. conflict). The message payload is stored as JSON verbatim; payload identity is deep JSON equality, so a backend that normalizes stored JSON (e.g. Postgres JSONB key ordering) still recognizes an exact replay. The caller owns transaction scoping — invoke this inside one transaction and pass callbacks bound to it. When every callback is synchronous the result is returned synchronously, so the algorithm also fits synchronous backends. | | `agent` | `value` | No-defaults agent builder. Exported as `agent` from `@fabric-harness/sdk/strict`. Every `init()` option must be declared explicitly — useful for Temporal replay determinism and audit/compliance workloads where implicit behaviour is unwelcome. | | `AgentAttemptMarker` | `type` | Harness-owned durable evidence that a submission attempt was started and has not yet settled. A coordinator inserts a marker immediately before starting an attempt and deletes it when the attempt settles; reconciliation treats a fresh marker as proof that the attempt may still be running and must not be reconciled as interrupted. | | `AgentDefinition` | `type` | Public export. | | `AgentDispatchAdmission` | `type` | Public export. | | `AgentDispatchReceipt` | `type` | Public export. | | `AgentDispatchRequest` | `type` | Async delivery request to a persistent agent instance + session. | | `AgentEvent` | `type` | Public export. | | `AgentEventBase` | `type` | Common envelope shared by every event variant. | | `AgentEventCallback` | `type` | Callback signature accepted by `init({ onEvent })`, `agent.session(id, { onEvent })`, and `session.prompt(text, { onEvent })`. | | `AgentEventType` | `type` | Public export. | | `AgentInit` | `type` | Public export. | | `AgentLoopRuntime` | `type` | Public export. | | `AgentSubmission` | `type` | Public export. | | `AgentSubmissionDurability` | `type` | Public export. | | `AgentSubmissionInput` | `type` | One admitted agent submission — the persisted operational payload for both transports. `kind` records how the submission arrived (`'dispatch'` via `dispatch()`, `'direct'` via the agent HTTP route); a dispatch's `submissionId` is the public `dispatchId` from its receipt. | | `AgentSubmissionStatus` | `type` | Public export. | | `AgentSubmissionStore` | `type` | Durable submission lifecycle storage. Stability: the lease method group mirrors the durable-execution engine and is subject to change until 1.0. This applies to every backend equally. | | `AgentTriggers` | `type` | Public export. | | `aiGateway` | `value` | Generic OpenAI-compatible AI gateway helper. Use for **any** gateway that speaks the OpenAI Chat Completions request/response shape: Helicone, Portkey, LiteLLM (self-hosted), Cloudflare AI Gateway, internal corp proxies, etc. See the package declarations for an example. For Vercel AI Gateway, prefer the `vercelAIGateway` preset — same factory under the hood with the gateway URL pre-baked. | | `AIGatewayOptions` | `type` | Public export. | | `AnthropicModelProvider` | `value` | Public export. | | `AnthropicProviderOptions` | `type` | Public export. | | `applyEstimatedCost` | `value` | Idempotently populate `usage.costUsd` from the static price table. Mutates `usage` and returns it. No-op when: - `usage` is undefined, - `usage.costUsd` is already set (provider supplied it directly), - no price row matches `modelRef`. | | `ApprovalCallback` | `type` | Public export. | | `ApprovalDecision` | `type` | Public export. | | `ApprovalNotification` | `type` | Public export. | | `ApprovalNotificationDeadLetter` | `type` | Public export. | | `ApprovalNotificationDeliveryStore` | `type` | Public export. | | `approvalNotificationFromEvent` | `value` | Public export. | | `approvalNotificationHandler` | `value` | Convert approval events into retryable, deduplicated notifications. This callback never throws. | | `ApprovalNotificationHandlerOptions` | `type` | Public export. | | `ApprovalNotificationState` | `type` | Public export. | | `ApprovalNotifier` | `type` | Public export. | | `ApprovalOptions` | `type` | Public export. | | `ApprovalPolicyRule` | `type` | Per-pattern approval metadata. Lets policy authors route specific tools/commands to a named audience (e.g. `'reviewer'`, `'compliance-team'`, `'project-admin'`). The audience id is opaque to fabric-harness — host applications map ids to humans via their own identity layer. | | `ApprovalRequest` | `type` | Public export. | | `ApprovalResponse` | `type` | Public export. | | `ApprovalRisk` | `type` | Public export. | | `ApprovalState` | `type` | Public export. | | `approvalStatesFromEntries` | `value` | Public export. | | `ApprovalStateStatus` | `type` | Public export. | | `ApprovalUnavailableStrategy` | `type` | Public export. | | `ApprovalVote` | `type` | Public export. | | `ArtifactCreateOptions` | `type` | Public export. | | `ArtifactRef` | `type` | Public export. | | `assertEnforceableNetworkPolicy` | `value` | Refuse production network policy when it can be bypassed by code in the sandbox. This validates an operator assertion; the named network boundary must still be provisioned by Docker, Kubernetes, or the cloud provider. | | `attachmentDigest` | `value` | Lowercase hex SHA-256 of the bytes (WebCrypto). | | `AttachmentLimitError` | `value` | Public export. | | `AttachmentPutInput` | `type` | Public export. | | `AttachmentRef` | `type` | Content-addressed descriptor of one stored attachment. | | `AttachmentStore` | `type` | Durable content-addressed attachment storage. - `put` is idempotent by `(scope, digest)` — re-putting the same content succeeds without duplicating storage (the first stored ref metadata is retained). It MUST verify the digest against the bytes and reject a mismatch with `AttachmentStoreError('DIGEST_MISMATCH')` before writing. - `get`/`getByAttachmentId` return `null` on a miss. - `delete` removes one stored attachment and throws `AttachmentStoreError('NOT_FOUND')` when nothing is stored under `(scope, digest)`. | | `AttachmentStoreError` | `value` | Public export. | | `attachSandbox` | `value` | Build a `SandboxFactory` that, when invoked, returns an `AttachedSandboxEnv` delegating to the registered sandbox without owning its lifecycle. Calling `cleanup()` on the attached env unregisters this attachment but does NOT tear down the underlying sandbox. Pass an in-process `SandboxRef` to attach within the same process, or a `SerializedSandboxRef` (from `session.sandboxRef({ portable: true })`) to rehydrate a sandbox handed off from another process. Cross-process refs require a decoder registered for `serialized.provider` via `registerSandboxRefDecoder()`. | | `AutonomyMode` | `type` | Public export. | | `AutonomyOptions` | `type` | Public export. | | `AzureOpenAIModelProvider` | `value` | Public export. | | `AzureOpenAIProviderOptions` | `type` | Public export. | | `BashInput` | `type` | Public export. | | `bashTool` | `value` | Public export. | | `BedrockModelProvider` | `value` | Public export. | | `BedrockProviderOptions` | `type` | Public export. | | `buildModelMessagesFromHistory` | `value` | Public export. | | `buildResultFollowUpPrompt` | `value` | Follow-up prompt sent when the LLM ends a turn without calling `finish` or `give_up`. | | `buildResultFooter` | `value` | Footer appended to user prompts/skill bodies when a `result` schema is set. | | `buildResultRetryPrompt` | `value` | Public export. | | `BuiltinFileTool` | `type` | Public export. | | `BuiltinTool` | `type` | Public export. | | `bytesToHex` | `value` | Public export. | | `CapabilityPolicy` | `type` | Public export. | | `CartesiaSttProvider` | `value` | Public export. | | `CartesiaSttProviderOptions` | `type` | Public export. | | `CartesiaTtsProvider` | `value` | Public export. | | `CartesiaTtsProviderOptions` | `type` | Public export. | | `chainSecretProviders` | `value` | Resolve from providers in order; errors fail closed instead of falling through. | | `Channel` | `type` | Public export. | | `ChannelContext` | `type` | Public export. | | `ChannelDispatch` | `type` | Public export. | | `ChannelDispatchRequest` | `type` | Public export. | | `ChannelRoute` | `type` | Channels turn platform webhooks (Slack, GitHub, …) into agent dispatches. Handlers are written against the Web `Request`/`Response` API and `crypto.subtle`, so the same channel runs on Node and Cloudflare. A channel is a stateless route container plus a conversation-id (de)serializer — session continuity falls out of the key (same thread → same key → same session). | | `CheckpointCreateOptions` | `type` | Public export. | | `CheckpointRestoreOptions` | `type` | Public export. | | `CheckpointResult` | `type` | Public export. | | `clampCommandTimeout` | `value` | Public export. | | `clampReadLimit` | `value` | Public export. | | `classifySubmissionState` | `value` | Classify how far a persisted submission input progressed. - `absent` — the input entry never landed in session history: the attempt crashed before applying it. Safe to requeue for a clean first attempt. - `completed` — finished work: a canonical settlement entry exists, an assistant response follows the input with no unresolved trailing tool batch, or a later user input shows the conversation moved on. Settle as success; never retry (retrying completed work is the one unrecoverable corruption). - `continuable` — the trailing turn carries unresolved tool calls. The next `session.prompt()` re... | | `CohereModelProvider` | `value` | Public export. | | `CohereProviderOptions` | `type` | Public export. | | `combineSubmissionTelemetrySinks` | `value` | Fan one event out to several sinks. | | `Command` | `type` | Public export. | | `CommandEnvValue` | `type` | Public export. | | `CommandPolicy` | `type` | Public export. | | `CommandToolInput` | `type` | Public export. | | `CommandToolOptions` | `type` | Public export. | | `CompactionOptions` | `type` | Public export. | | `CompactionResult` | `type` | Public export. | | `configureDispatchRuntime` | `value` | Configure the ambient dispatch queue used by `dispatch`. | | `configureJobInvocationRuntime` | `value` | Public export. | | `connectFabricVoice` | `value` | Public export. | | `connectFabricWs` | `value` | Public export. | | `connectMcpServer` | `value` | Public export. | | `consoleTelemetryExporter` | `value` | Telemetry exporter that writes spans through the SDK logger (default `console`-backed). Useful for local development and as a fallback when no OpenTelemetry collector is wired up. Use `openTelemetryExporter` or `langfuseExporter` for production. | | `ContextBudget` | `type` | Public export. | | `ContextBudgetOptions` | `type` | Public export. | | `CONVERSATION_STREAM_DEFAULT_READ_LIMIT` | `value` | Public export. | | `CONVERSATION_STREAM_MAX_READ_LIMIT` | `value` | Public export. | | `conversationKey` | `value` | Public export. | | `ConversationProducerClaim` | `type` | Public export. | | `ConversationProjector` | `value` | Public export. | | `ConversationStreamAppendInput` | `type` | Public export. | | `ConversationStreamBatch` | `type` | Public export. | | `ConversationStreamIdentity` | `type` | Public export. | | `ConversationStreamMeta` | `type` | Public export. | | `conversationStreamPath` | `value` | Stream path for a session's conversation projection. | | `ConversationStreamReadResult` | `type` | Public export. | | `ConversationStreamRecord` | `type` | Append-only conversation stream — the durable, offset-addressable projection of a session's active path (v2 migration, phase A4). The SessionEntry DAG remains the single source of truth for model context; this stream exists so clients can read a conversation with offsets (catch-up + live tail), across processes, without count-based replay. The projection appends one record per active-path entry, and — because the DAG can branch (fork/replay/checkpoint-restore) while a stream cannot — an explicit `truncated` record whenever the active path rewinds, paired with a producer-epoch bump so stale... | | `ConversationStreamStore` | `type` | Durable append-only conversation stream storage. **Batch atomicity is a hard contract requirement**: every record in an `append` must be persisted together under one offset, all-or-nothing. First-party adapters satisfy this by serializing the batch into a single row/document write; an adapter that splits records across non-atomic writes violates the contract. **Producer fencing**: `acquireProducer` bumps the producer epoch; appends carrying a stale epoch are rejected. The (path, producerId, epoch, sequence) uniqueness makes redelivered appends idempotent — a retried append with the same coo... | | `ConversationStreamStoreError` | `value` | Public export. | | `CostBudgetStore` | `type` | Async store for cross-process spend aggregation. Pair with `CostLimit.scopeKey` + `CostLimit.perScope` to enforce a budget that survives process restarts — "tenant:acme spends ≤ $50 today" or "company-wide ≤ $100 this hour". Built-in implementations: - `inMemoryCostBudgetStore()` — process-local; default. - `@fabric-harness/node` exposes `postgresCostBudgetStore({ pool })`. | | `CostBudgetTracker` | `value` | Tracks cumulative session spend. Cheap to construct; one per session. | | `CostLimit` | `type` | Public export. | | `CostLimitContext` | `type` | Public export. | | `CostLimitExceededError` | `value` | Public export. | | `createAgent` | `value` | Define a persistent, URL-addressable agent. Files in `.fabricharness/agents/` default-export `createAgent(...)`; the runtime resolves a fresh config per interaction and keeps sessions across direct prompts and dispatched inputs. | | `createAttachmentRef` | `value` | Build an `AttachmentRef` for the given bytes, computing the SHA-256 digest via WebCrypto (`crypto.subtle`) so the SDK stays runtime-agnostic. | | `createBuiltinTools` | `value` | Public export. | | `createCommandTools` | `value` | Public export. | | `createConsoleLogger` | `value` | Build a Console-backed logger with an explicit level. Useful for tests that want to capture or silence SDK output without touching globals. | | `CreatedAgent` | `type` | A persistent, addressable agent created with `createAgent`. Distinct from a finite `agent({ run })` job: it has no `run` — the initializer returns configuration, and the runtime maintains sessions across interactions. | | `createDirectAgentSubmissionInput` | `value` | Mint a direct-prompt submission input with a fresh submission id. | | `createDispatchAgentSubmissionInput` | `value` | Map a `DispatchInput` onto the persisted submission input shape. | | `createFabricContext` | `value` | Public export. | | `createFabricFs` | `value` | Adapt a sandbox into the public filesystem convenience surface. | | `createFileTools` | `value` | Public export. | | `createMcpAuthorizationCodeAuth` | `value` | Authorization-code + PKCE provider; the MCP SDK refreshes stored tokens automatically. | | `createMcpClientCredentialsAuth` | `value` | OAuth client-credentials provider with MCP SDK token refresh handling. | | `createMcpTools` | `value` | Public export. | | `CreateMcpToolsOptions` | `type` | Public export. | | `createObservabilityObserver` | `value` | Create a fail-open event observer suitable for Braintrust, Sentry, Jetty, or a custom sink. | | `createObservabilityRecord` | `value` | Convert a Fabric event into a vendor-neutral, low-cardinality record. | | `createOperationalMetricsCollector` | `value` | Low-cardinality operational metrics collector suitable for OTel/Prometheus bridging. | | `createPiAgentLoopRuntime` | `value` | Public export. | | `createRemoteSandboxEnv` | `value` | Wrap a provider-owned remote sandbox client in Fabric's SandboxEnv contract. Provider credentials and SDK objects remain outside model context/history; Fabric only sees the narrow file/shell/snapshot API exposed here. | | `createResultTools` | `value` | Produce the per-call `finish` and `give_up` tool pair for a given ResultValidator. - `finish`'s parameters are a generic JSON Schema object because we can't derive a precise schema from `ResultValidator`. The validator's `safeParse` handles actual validation. - First successful `finish` (or `give_up`) call wins. Subsequent calls return an error tool result rather than throwing, to keep the conversation transcript natural. | | `createSandboxEnv` | `value` | Public export. | | `createScopedSandboxEnv` | `value` | Return a view of a sandbox with a narrower default cwd. Relative file paths and shell cwd values are resolved from this scoped cwd while the underlying sandbox still enforces its workspace boundary. | | `createSearchTool` | `value` | Exposes a `Retriever` to the model as a `search` tool. The tool is read-only; wrap it with a governance decorator to stamp lineage or route approvals. | | `createStdioMcpClient` | `value` | Public export. | | `createSubmissionRunner` | `value` | Public export. | | `createVirtualSandboxEnv` | `value` | Public export. | | `CredentialMissingStrategy` | `type` | Public export. | | `currentJobInvocation` | `value` | Public export. | | `currentSubmissionContext` | `value` | The submission owning the current execution, or `undefined` outside one. | | `DeepgramSttProvider` | `value` | Public export. | | `DeepgramSttProviderOptions` | `type` | Public export. | | `DEFAULT_HEADLESS_PREAMBLE` | `value` | Public export. | | `defaultLoopRuntime` | `value` | Public export. | | `defaultModelProvider` | `value` | Public export. | | `defaultSessionStore` | `value` | Public export. | | `defineAction` | `value` | Define an action — the harness-context counterpart to `defineTool`. A tool is a leaf capability the model calls; an action holds the harness (`context.init`) and can prompt, spawn sessions, and compose other work. Input/output use the harness `schema` builders and are validated on every `runAction` call; outputs must be JSON-serializable. | | `defineAgent` | `value` | Canonical v2 name for `createAgent`: persistent, URL-addressable agents are *defined*, and the runtime instantiates them per addressed id. `createAgent` remains supported as an alias. | | `defineChannel` | `value` | Validates and brands a channel's routes. | | `defineCommand` | `value` | Public export. | | `DefinedAgent` | `type` | Public export. | | `defineJob` | `value` | No-defaults finite-job builder with lazy default-session helpers. | | `defineTool` | `value` | Public export. | | `defineWebhookSubscription` | `value` | Helper that returns the definition unchanged. Useful for type inference and to keep agent files declarative. See the package declarations for an example. | | `DeletionCompletionRecord` | `type` | Public export. | | `DeliveredAttachment` | `type` | One attachment on a `kind: 'user'` message. Today the only supported attachment is an image, carried either inline (`data`, base64) or as a durable content-addressed reference (`ref`) once an attachment store is configured — admission materializes inline bytes into refs. An attachment must carry `data` or `ref` (or both, transiently during materialization). | | `DeliveredAttachmentRef` | `type` | Durable reference to attachment bytes in an attachment store. | | `DeliveredMessage` | `type` | DeliveredMessage — the single unified input shape for everything that enters a persistent agent's session: direct HTTP prompts, dispatch, channels/webhooks, Databricks events, SDK clients, and tests. `kind: 'user'` is a direct user talking to the assistant (1:1 chat surface), optionally carrying attachments. `kind: 'signal'` models everything beyond that direct exchange — a Slack thread or a Lakeflow job event is activity the agent observes, not the assistant's own user speaking. Sender identity and structured metadata go in `attributes`; the message itself in `body`. Signals render into mo... | | `deliveredSignalToEntryData` | `value` | Map a signal-kind message onto the persisted `signal` entry's data shape. | | `deriveCompactionDefaults` | `value` | Compute model-aware compaction defaults. Reserve is capped at the model's max output because reserving more than the model can emit in one turn wastes context; the preserved tail stays flat because recent-context fidelity depends on the active work, not on the model's total window size. | | `dispatch` | `value` | Public export. | | `DispatchInput` | `type` | Internal enqueued form, carrying correlation + isolation metadata. | | `DispatchProcessor` | `type` | Consumes enqueued dispatches and applies them to an instance session. | | `DispatchQueue` | `type` | Admission queue for dispatches. The default is in-process; durable backends implement the same shape. | | `DispatchReceipt` | `type` | Acceptance confirmation for an enqueued dispatch. | | `DockerSandboxEnv` | `value` | Public export. | | `DockerSandboxOptions` | `type` | Public export. | | `DURABILITY_DEFAULT_MAX_ATTEMPTS` | `value` | Default maximum total attempts before terminalization. | | `DURABILITY_DEFAULT_TIMEOUT_MS` | `value` | Default submission timeout in milliseconds (one hour). | | `DurableSessionRuntime` | `type` | Structural delegate for durable session execution. When `init()` is given a `sessionRuntime` factory that produces one of these, the SDK's session calls (`prompt`, `task`, `shell`, `checkpoint.*`) are routed through the runtime instead of executing inline. This is the seam for Temporal workflows, external orchestration runtimes, or test fakes. `mount`, `history`, `artifact`, and `compact` remain SDK-local concerns and are not delegated — they operate against the local session store. | | `DurableSessionRuntimeFactory` | `type` | Public export. | | `editFileTool` | `value` | Public export. | | `EditInput` | `type` | Public export. | | `ElevenLabsTtsProvider` | `value` | Public export. | | `ElevenLabsTtsProviderOptions` | `type` | Public export. | | `EmbeddingProvider` | `type` | Embeddings seam. Feeds self-managed-embedding vector indexes (embed the query → query vector) and any bring-your-own retrieval pipeline. Returns one vector per input text, order-preserving. | | `emitOpenTelemetrySpan` | `value` | Public export. | | `emitSubmissionTelemetry` | `value` | Deliver an event to a sink, swallowing (and reporting) sink failures. | | `EmptySandboxEnv` | `value` | Public export. | | `enqueueDispatch` | `value` | Validate + normalize a named request and enqueue it, generating the dispatch id. | | `entryToTelemetrySpan` | `value` | Public export. | | `environmentSecretProvider` | `value` | Runtime-only environment provider with optional prefix and explicit allowlist. | | `EnvironmentSecretProviderOptions` | `type` | Public export. | | `estimateCostUsd` | `value` | Estimate USD cost for a single model call. Returns `0` when no row matches `modelRef` — callers should treat 0 as "unknown" and not overwrite an existing `costUsd` from the provider. | | `estimateModelMessagesTokens` | `value` | Public export. | | `estimateSessionEntriesTokens` | `value` | Public export. | | `estimateTextTokens` | `value` | Public export. | | `evaluateCommandPolicy` | `value` | Public export. | | `evaluateContextBudget` | `value` | Public export. | | `evaluateNetworkPolicy` | `value` | Evaluate a URL or `Request` against the configured network policy. Returns `{ allowed: true }` when the request is permitted, otherwise a denial with the reason and matched pattern. | | `evaluateOperationalSlos` | `value` | Public export. | | `evaluateToolCallPolicy` | `value` | Public export. | | `eventToTelemetrySpan` | `value` | Public export. | | `ExistsInput` | `type` | Public export. | | `existsTool` | `value` | Public export. | | `extractResultValue` | `value` | Public export. | | `FABRIC_OPERATIONAL_METRICS` | `value` | Public export. | | `FabricActor` | `type` | Public export. | | `FabricAgent` | `type` | Public export. | | `FabricContext` | `type` | Public export. | | `FabricError` | `value` | Public export. | | `FabricErrorCode` | `type` | Public export. | | `FabricErrorOptions` | `type` | Public export. | | `FabricEvent` | `type` | Public export. | | `FabricEventCallback` | `type` | Public export. | | `FabricEventType` | `type` | Public export. | | `FabricFs` | `type` | Out-of-band filesystem surface for a session sandbox. These operations do not write to conversation history and are intended for host-side plumbing: staging files, collecting artifacts, and preparing scratch space. If the model should reason about a file, prompt it to use the normal read/write/edit tools instead. | | `FabricObservabilityRecord` | `type` | Public export. | | `FabricPrincipal` | `type` | The governed identity a piece of work runs as (v2). Distinct from `ActorIdentity` (who asked): the principal is what the platform's access control enforces — a human user, a machine service principal, or a hosted app's own identity. `ucPrincipal` carries the catalog-governance principal name when the platform has one (e.g. Unity Catalog). | | `FabricRuntime` | `type` | Execution runtime selection. - `inline` (default): single-process execution. Uses the configured `SessionStore` (in-memory by default) for history, artifacts, approvals. - `stateless`: explicit headless / ephemeral mode. No session store, no artifact persistence, no approval waiting. Each invocation is independent. Use for high-volume webhook agents and edge runtimes where state would just be discarded anyway. In production (`FABRIC_ENV=production` or `NODE_ENV=production`) this mode must be selected explicitly — `inline` without an explicit store will warn or fail depending on `FABRIC_ALLO... | | `FabricSession` | `type` | Public export. | | `FallbackModelProvider` | `value` | Public export. | | `FallbackModelProviderOptions` | `type` | Public export. | | `FileStat` | `type` | Public export. | | `FilesystemEntry` | `type` | Public export. | | `FilesystemPolicy` | `type` | Public export. | | `FilesystemSource` | `type` | A read-only content source that can be mounted into a sandbox at sandbox-creation time. The agent then has built-in `read`, `glob`, and `grep` tools available over the mounted content — no retrieval pipeline, no embeddings, no vector store required. Sources are intentionally minimal: they yield (path, content) pairs. Implementations decide how to enumerate (eager vs lazy is up to the source author) — the mount step pulls the full set into the sandbox. | | `findSubmissionInputIndex` | `value` | Index of the (last) `user_prompt` entry carrying the submission id, or -1. | | `findTrailingDanglingToolCalls` | `value` | Find trailing `tool_call` entries on the active path that were never settled — no matching `tool_result` (paired by `toolCallId`, falling back to tool name) and no subsequent `error` entry for the same tool. A dangling call means a model turn died (crash/abort) between recording the call and recording its outcome. Left in place it produces an assistant `tool_use` with no `tool_result` on resume, which providers reject — the repair path appends synthetic interrupted outcomes for exactly the entries returned here. Conservative by construction: only the window after the last turn boundary (use... | | `findTrailingUnfinishedTasks` | `value` | Trailing `task_start` entries in the same window with no matching `task_end` — a subtask that was in flight when the turn died. These do not corrupt model context (task entries are bookkeeping), but settling them keeps UI/audit state coherent. | | `formatSchemaIssues` | `value` | Public export. | | `formatStreamOffset` | `value` | Public export. | | `fumadocsSource` | `value` | Mount a local Fumadocs content directory as a knowledge base. Strips MDX frontmatter by default for cleaner agent context. For a published Fumadocs site, fetch its `llms.txt` / sitemap and pass the URLs to `httpFilesystemSource`. | | `GeminiModelProvider` | `value` | Public export. | | `GeminiProviderOptions` | `type` | Public export. | | `generateAffinityKey` | `value` | Generate a deterministic `aff_\u003cULID\u003e` affinity key from an `(agentId, sessionId)` pair. The same pair always produces the same key, which is stable across restarts. Different pairs produce different keys with overwhelming probability. | | `generateWithRuntime` | `value` | Public export. | | `getAgentDefinition` | `value` | Public export. | | `getCreatedAgent` | `value` | Return the `CreatedAgent` carried by a value, or `undefined`. | | `getLogger` | `value` | Get the currently configured logger. | | `getVirtualSandbox` | `value` | One-liner helper for the most common pattern: mount a single read-only source into a virtual sandbox. Equivalent to: See the package declarations for an example. Used for support agents, runbook lookup, FAQ assistants — anywhere a small Markdown corpus needs to be searchable via the agent's built-in `grep`/`glob`/`read` tools. See the package declarations for an example. | | `GlobInput` | `type` | Public export. | | `globTool` | `value` | Public export. | | `GrepInput` | `type` | Public export. | | `GrepMatch` | `type` | Public export. | | `grepTool` | `value` | Public export. | | `hasSubmissionSettledEntry` | `value` | True when the path carries a canonical `submission_settled` entry for the id. | | `hexToBytes` | `value` | Public export. | | `hmacSha256` | `value` | Public export. | | `httpFilesystemSource` | `value` | Fetch a list of URLs and mount each response body as a file. Useful for pulling a small published docs set into the sandbox so the built-in `read`/`grep`/`glob` tools can search it like local files. | | `HttpResource` | `type` | Public export. | | `init` | `value` | Public export. | | `initializePersistentAgent` | `value` | Resolve a persistent agent's config for an instance and build a `FabricAgent`. Runtime-specific resources (session store, workspace roles/skills, loop runtime) are layered by the host that calls this. | | `inMemoryApprovalNotificationStore` | `value` | Process-local atomic delivery state for development and single-process hosts. | | `InMemoryAttachmentStore` | `value` | In-memory attachment store (dev / `runtime: 'stateless'` / tests). | | `InMemoryConversationStreamStore` | `value` | In-memory conversation stream store (dev / `runtime: 'stateless'` / tests). | | `inMemoryCostBudgetStore` | `value` | Process-local cost budget store. Default when `store` is not provided. | | `InMemoryDispatchQueue` | `value` | In-process dispatch queue: microtask-drained, concurrent across sessions, serialized within a single session. Suitable for the `inline`/`stateless` runtimes and dev. Durable delivery (surviving restarts) is provided by the Temporal-backed queue, which implements this same interface. | | `inMemorySessionMemory` | `value` | Process-local in-memory implementation. Default when `init({ memory })` is not configured — pair with Postgres for durability across restarts. | | `InMemorySessionStore` | `value` | Public export. | | `inMemorySource` | `value` | Build a source from an in-memory map of `path -> content`. Useful for tests, fixtures, and small static knowledge bases bundled into the agent module itself. | | `InMemorySubmissionStore` | `value` | Public export. | | `InterruptedToolCallRef` | `type` | A tool call settled with an explicit interrupted-outcome marker at terminalization. | | `InvalidDeliveredMessageError` | `value` | Thrown by `parseDeliveredMessage` on malformed input. | | `invoke` | `value` | Public export. | | `isActionDefinition` | `value` | Public export. | | `isContextOverflowError` | `value` | Public export. | | `isCreatedAgent` | `value` | Whether a value is a `CreatedAgent`. | | `isDeliveredMessageShape` | `value` | True when a raw value already looks like a `DeliveredMessage` (has a valid `kind`). | | `isEvent` | `value` | Type guard: narrow an `AgentEvent` to a specific variant. See the package declarations for an example. | | `isFabricError` | `value` | Public export. | | `isInMemoryStore` | `value` | Returns true when `store` is the in-memory default (no `appendEntry` persistence beyond memory). Used by stateless mode to skip writes. | | `isStatelessRuntime` | `value` | Public export. | | `isSubmissionPayload` | `value` | Validate that a parsed JSON payload matches the expected submission shape. Used after deserializing a persisted payload to verify the object is a well-formed `AgentSubmissionInput` that is consistent with the stored submission metadata. Both dispatch and direct payloads carry the same `message: DeliveredMessage` field — validated identically here regardless of transport `kind`. | | `isValidAffinityKey` | `value` | Public export. | | `job` | `value` | No-defaults agent builder. Exported as `agent` from `@fabric-harness/sdk/strict`. Every `init()` option must be declared explicitly — useful for Temporal replay determinism and audit/compliance workloads where implicit behaviour is unwelcome. | | `JobContext` | `type` | Runtime-ready context for finite jobs. The default session is initialized lazily. | | `JobDefinition` | `type` | Public export. | | `JobInvocationContext` | `type` | Public export. | | `JobInvocationOptions` | `type` | Public export. | | `JobInvocationReceipt` | `type` | Public export. | | `JobInvocationRuntime` | `type` | Public export. | | `JobMiddleware` | `type` | Public export. | | `JournalCallbacks` | `type` | Public export. | | `jsonDeepEqual` | `value` | Structural equality over JSON values (objects compared key-order-insensitively). | | `JsonObject` | `type` | Public export. | | `JsonPrimitive` | `type` | Public export. | | `JsonSchemaObject` | `type` | Public export. | | `JsonValue` | `type` | Public export. | | `LangfuseClientLike` | `type` | Optional Langfuse exporter. Adapts Fabric's `TelemetrySpan` shape to Langfuse's tracing API. The Langfuse client is provided by the caller — we don't take a hard dependency. Install peer dep: See the package declarations for an example. Usage: See the package declarations for an example. | | `langfuseExporter` | `value` | Public export. | | `LangfuseExporterOptions` | `type` | Public export. | | `LEASE_DURATION_MS` | `value` | Default lease duration for submission ownership in milliseconds (30 seconds). | | `listModelPrices` | `value` | All currently-registered rows (newest-last). Returns a copy. | | `listSandboxRefDecoders` | `value` | Returns the list of currently registered providers. | | `localDirectorySource` | `value` | Read a host directory recursively as a read-only source. `include` is called for each candidate file path (relative to `hostPath`). Return `false` to skip. Defaults to including everything. | | `LocalSandboxEnv` | `value` | Public export. | | `LocalSandboxOptions` | `type` | Public export. | | `Logger` | `type` | Minimal logger seam used by the SDK for non-event diagnostic output (warnings, deprecation notices, telemetry fallbacks). All `console.*` inside the SDK should route through `getLogger()` so ops teams can redirect or silence messages in production. The default logger writes to `console` and respects `FABRIC_HARNESS_LOG_LEVEL=debug\|info\|warn\|error\|silent` (default `warn`). | | `LogLevel` | `type` | Public export. | | `lookupModelPrice` | `value` | Look up the most recently-registered row matching `modelRef`. `modelRef` can be: - `'provider/model'` (preferred, e.g. `'openai/gpt-4o'`) - `'model'` alone (e.g. `'gpt-4o'`) — first row whose model matches wins Provider matching is case-insensitive. Model matching is exact. | | `materializeMessageAttachments` | `value` | Materialize a message's inline attachments into durable refs: decode the base64 data, store the bytes under `scope`, and return a NEW message whose attachments carry `{ type, mimeType, filename?, ref }` and no `data`. Deterministic by construction — attachment ids are `${idPrefix}_${index}` and digests derive from content — so an exact redelivery of the same message produces an identical materialized payload (admission idempotency). Messages without inline attachments are returned unchanged (same reference). | | `MAX_ATTACHMENT_DATA_LENGTH` | `value` | Maximum accepted base64 length for a single inline attachment. | | `McpAuthorizationCodeOptions` | `type` | Public export. | | `McpAuthorizationCodeState` | `type` | Public export. | | `McpClientCredentialsOptions` | `type` | Public export. | | `McpClientLike` | `type` | Public export. | | `McpServerConnection` | `type` | Public export. | | `McpServerOptions` | `type` | Public export. | | `McpToolDescriptor` | `type` | Public export. | | `McpTransport` | `type` | Public export. | | `messageHasDataAttachments` | `value` | True when the message carries at least one inline (base64) attachment. | | `mintlifySource` | `value` | Mount a checked-out Mintlify content directory. For a hosted Mintlify MCP server, use `connectMcpServer('mintlify', { url, transport: 'streamable-http' })` instead. | | `MissingInputStrategy` | `type` | Public export. | | `MkdirInput` | `type` | Public export. | | `mkdirTool` | `value` | Public export. | | `MockModelProvider` | `value` | Public export. | | `ModelAttemptEvent` | `type` | Public export. | | `ModelConfig` | `type` | Public export. | | `ModelMessage` | `type` | Public export. | | `ModelMessageRole` | `type` | Public export. | | `ModelMetadata` | `type` | Public export. | | `ModelPriceRow` | `type` | Static USD price table for model providers. Used to populate `ModelUsage.costUsd` on responses that don't include billing info from the provider directly (most providers — only `pi-loop-runtime` and a handful of gateways report cost). Prices are stamped with `effectiveAt`. The table is best-effort: real billing reconciliation should use vendor invoices. Override or extend at runtime with `registerModelPrices` for custom-rate contracts. | | `ModelPricingUsage` | `type` | Public export. | | `ModelProvider` | `type` | Public export. | | `ModelProviderFactory` | `type` | Resolves a parsed `provider/model-id` ref into a concrete provider. Registered factories let out-of-core packages (e.g. | | `ModelRequest` | `type` | Public export. | | `ModelResponse` | `type` | Public export. | | `ModelRuntimeOptions` | `type` | Public export. | | `ModelStreamChunk` | `type` | Public export. | | `ModelToolCall` | `type` | Public export. | | `ModelToolSchema` | `type` | Public export. | | `ModelUsage` | `type` | Public export. | | `MountedSource` | `type` | Public export. | | `MountResult` | `type` | Public export. | | `NamedAgentDispatchRequest` | `type` | A dispatch request that names its target agent. | | `NamedJobInvocation` | `type` | Public export. | | `NativeLoopRuntime` | `value` | Public export. | | `NetworkEnforcementLayer` | `type` | Public export. | | `NetworkEnforcementRequirement` | `type` | Public export. | | `NetworkPolicy` | `type` | Public export. | | `noopSessionStore` | `value` | Public export. | | `NoopSessionStore` | `value` | No-op session store for `runtime: 'stateless'` mode. All writes are discarded; reads always return the empty initial session. Use this when the agent is intended as a pure request/response handler with no persistence (typical for high-volume webhooks and edge runtimes). Approvals and artifact retrieval are not supported — wiring those up requires a real store. Callers in stateless mode should not rely on artifact persistence or approval gating. | | `normalizeDeliveredMessage` | `value` | Normalize legacy inputs into a `DeliveredMessage`: - a string → a user message with that body - a value with a `kind` discriminator → validated as a DeliveredMessage - any other JSON value → a user message with the JSON-stringified body (matching the historical dispatch rendering, so behavior is unchanged for pre-DeliveredMessage callers) | | `ObservabilityCorrelation` | `type` | Public export. | | `ObservabilityObserverOptions` | `type` | Public export. | | `openAIChatCompletionToModelResponse` | `value` | Public export. | | `OpenAICompatibleModelProvider` | `value` | Public export. | | `OpenAICompatibleProviderOptions` | `type` | Public export. | | `OpenAIRealtimeVoiceProvider` | `value` | OpenAI Realtime voice provider. Connects via WebSocket; emits `audio_delta` / `text_delta` / `transcript` / `tool_call` / `response_done` events. No transitive dependency on `ws` — uses the global `WebSocket` available on Node 22+ and browsers. See the package declarations for an example. | | `OpenAIRealtimeVoiceProviderOptions` | `type` | Public export. | | `openTelemetryExporter` | `value` | Bridge Fabric's SDK-neutral TelemetrySpan into a real | | `OpenTelemetryExporterOptions` | `type` | Public export. | | `OperationalMetricsCollector` | `type` | Public export. | | `OperationalMetricsSnapshot` | `type` | Public export. | | `OperationalSloEvaluation` | `type` | Public export. | | `OperationalSloTargets` | `type` | Public export. | | `parseConversationKey` | `value` | Public export. | | `ParsedConversationKey` | `type` | Public export. | | `parseDeliveredMessage` | `value` | Validate a raw value as a `DeliveredMessage`. Shared by dispatch admission and the direct HTTP route so every transport produces the same structured error on bad input. | | `ParsedModelRef` | `type` | Public export. | | `parseModelRef` | `value` | Public export. | | `parsePersistentSessionId` | `value` | Inverse of `persistentStoreSessionId`: decode a store session id back into `{ agent, instanceId, session }`, or `undefined` if it is not a persistent-instance key. Useful for admin surfaces that list raw session ids. | | `parseRetryAfterMs` | `value` | Parse a `Retry-After` header value (RFC 7231) into milliseconds. Accepts both delta-seconds and HTTP-date forms. Returns undefined when the value is missing or unparseable. | | `parseStreamOffset` | `value` | Public export. | | `PersistenceBundle` | `type` | Complete persistence surface consumed by a Fabric host. | | `PersistenceDeleteResult` | `type` | Public export. | | `PersistenceHealth` | `type` | Public export. | | `PersistentAgentConfig` | `type` | Runtime configuration returned by a `createAgent` initializer. Mirrors the agent-level slice of `AgentInit`; `instructions` becomes the session system prompt (a role), and `subagents` map to named roles. | | `PersistentAgentContext` | `type` | Per-interaction context passed to a `createAgent` initializer. `id` is the URL `<id>` of the addressed instance (or the dispatch target id); `env` is the platform environment supplied by the runtime. | | `persistentConfigToAgentInit` | `value` | Translate a `PersistentAgentConfig` into an `AgentInit`. | | `PersistentSessionIdentity` | `type` | Decoded identity of a persistent instance session store key. | | `persistentStoreSessionId` | `value` | Store-session key for a persistent instance's named session. Collapses the `(agentName, instanceId, sessionName)` identity onto Fabric's single-string `sessionId`, keeping persistent sessions inside the existing session stores. | | `PiAgentLoopRuntime` | `value` | Public export. | | `PiAgentLoopRuntimeOptions` | `type` | Public export. | | `PiCustomModel` | `type` | Public export. | | `PipelineVoiceProvider` | `value` | Public export. | | `PipelineVoiceProviderOptions` | `type` | Public export. | | `policiedFetch` | `value` | Public export. | | `PoliciedFetchOptions` | `type` | Wrap a `fetch`-like function with `CapabilityPolicy` enforcement. Tools and connectors that make outbound HTTP should accept a custom `fetch` and pass the result of `policiedFetch(fetch, policy)`. Throws a `FabricError` (`POLICY_DENIED`) when a request violates the policy; never sends a forbidden request. | | `policiedSandboxEnv` | `value` | Wrap a `SandboxEnv` with `CapabilityPolicy` enforcement at the sandbox layer. Without this decorator, agent code that calls `(await session.sandbox).exec(...)` directly bypasses the policy that `session.shell()` and tool dispatch enforce. `policiedSandboxEnv()` closes that gap by re-running the same policy evaluation against `exec` / `readFile` / `writeFile` / `mkdir` / `rm` / etc. Throws `FabricError`: - `COMMAND_DENIED` when an exec is denied or requires approval. - `POLICY_DENIED` when a filesystem op is denied or requires approval. The decorator does NOT auto-resolve `requireApproval` p... | | `PolicyDecision` | `type` | Public export. | | `PromptOptions` | `type` | Public export. | | `PromptRunInput` | `type` | Public export. | | `PromptRunResult` | `type` | Public export. | | `ProviderHttpError` | `value` | Public export. | | `ProvidersConfig` | `type` | Public export. | | `ProviderSettings` | `type` | Public export. | | `pruneSnapshots` | `value` | Public export. | | `RateLimiter` | `type` | Public export. | | `RateLimiterAcquireOptions` | `type` | Process-local rate limiter for outbound provider calls. Use to prevent a fleet of agents from stampeding a single API key when the host runs many sessions in parallel. The default token-bucket impl is in-memory; Redis-backed implementations are a v1.8+ topic. fabric-harness ships this as a generic primitive — the same limiter can be reused for outbound HTTP calls inside connectors, webhook fan-out, or anywhere else throttling is useful. | | `ReaddirInput` | `type` | Public export. | | `readdirTool` | `value` | Public export. | | `ReadFileBufferInput` | `type` | Public export. | | `readFileBufferTool` | `value` | Public export. | | `ReadFileInput` | `type` | Public export. | | `readFileTool` | `value` | Public export. | | `readJsonBody` | `value` | Reads the body once and returns the raw bytes, the decoded text, and the parsed JSON together — so a channel can HMAC-verify the exact bytes and use the JSON without re-reading the (already consumed) stream. Returns undefined only when the body exceeds `limitBytes`. | | `readRequestBody` | `value` | Reads the full request body as bytes, or returns undefined if it exceeds `limitBytes`. NOTE: this consumes the request stream (single read). Signature-verifying channels need the *exact* bytes for HMAC and the parsed JSON afterward — don't call `request.json()` as well. Use `readJsonBody` to get both from one read. | | `redactError` | `value` | Public export. | | `RedactionOptions` | `type` | Public export. | | `redactJson` | `value` | Public export. | | `redactText` | `value` | Public export. | | `registerCreatedAgentName` | `value` | Register a name for a `CreatedAgent` so `dispatch(agent, ...)` can resolve it. | | `registeredModelProviders` | `value` | Names of externally registered providers, for diagnostics. | | `registerJobName` | `value` | Public export. | | `registerModelPrices` | `value` | Add or override price rows. Later rows take precedence over earlier ones. | | `registerModelProvider` | `value` | Register a model provider resolvable via `FABRIC_MODEL=<name>/<model-id>`. Built-in providers always take precedence; registering a name a built-in already owns has no effect on routing. Idempotent by name (last registration wins). Call at module import time. | | `registerSandbox` | `value` | Register a sandbox in the in-process registry and return a portable ref. Subsequent calls for the same env return the same ref. | | `registerSandboxRefDecoder` | `value` | Register a decoder for `provider` so `attachSandbox(serialized)` can rehydrate a sandbox from another process. Typically called once at startup by the package that owns the provider integration (e.g. `@fabric-harness/connectors/e2b` registers the `e2b` provider). | | `RemoteSandboxApi` | `type` | Public export. | | `RemoteSandboxOptions` | `type` | Public export. | | `renderDeliveredMessage` | `value` | Render a delivered message to the prompt text form. User messages pass their body through verbatim; signals render as their XML envelope via the shared `renderSignalMessage` machinery. | | `RequestBody` | `type` | Public export. | | `resetDispatchRuntime` | `value` | Clear the ambient dispatch runtime (tests/teardown). | | `resetJobInvocationRuntime` | `value` | Public export. | | `resetModelPricesToBuiltins` | `value` | Reset the registry to the built-in seed (test/utility). | | `ResolvedModelProvider` | `type` | Public export. | | `resolveModelProvider` | `value` | Public export. | | `ResolveModelProviderOptions` | `type` | Public export. | | `resolveRuntimeMode` | `value` | Resolve the effective runtime mode for an `init()` call, applying production safety rules: - In production (`FABRIC_ENV=production` or `NODE_ENV=production`), choosing `stateless` is allowed but logged as an explicit choice. - `inline` (the default) without an explicit `SessionStore` falls back to in-memory storage. In production this emits a warning unless `FABRIC_ALLOW_EPHEMERAL_STATE=1` is set or runtime is explicitly `'stateless'`. - Unknown runtime values fall through to `'inline'` with a warning. | | `RESULT_END_DELIMITER` | `value` | Public export. | | `RESULT_START_DELIMITER` | `value` | Public export. | | `ResultExtractionOptions` | `type` | Public export. | | `ResultOutcome` | `type` | Public export. | | `ResultToolBundle` | `type` | Public export. | | `ResultUnavailableError` | `value` | Thrown when the LLM calls the `give_up` tool, indicating it cannot produce a result that conforms to the required schema. | | `ResultValidator` | `type` | Public export. | | `RetrievedChunk` | `type` | Generic, provider-agnostic retrieval seam. RAG is query-time, so it does NOT fit `FilesystemSource` (an eager full-dump mount) — a retriever resolves the top matches for a query on demand. Databricks Vector Search is the flagship implementation, but this is reusable for any vector store. | | `RetrieveOptions` | `type` | Public export. | | `Retriever` | `type` | Public export. | | `RmInput` | `type` | Public export. | | `rmTool` | `value` | Public export. | | `Role` | `type` | Public export. | | `runAction` | `value` | Validate input, run the action against `host`, validate + JSON-clone the output. The returned value is always safely serializable (a fresh JSON clone), so callers can persist or transmit it without sharing references. | | `RuntimeModeResolution` | `type` | Public export. | | `runWithJobInvocation` | `value` | Public export. | | `runWithSubmissionContext` | `value` | Run `fn` with `context` as the ambient submission correlation. | | `SandboxAdapterDescriptor` | `type` | Adapter expectations: - Every backend must expose the SandboxEnv contract above and map paths into a scoped workspace. - Secrets and provider credentials must stay in adapter-owned environment/config, not model context. - exec should enforce backend-specific command, network, and timeout policy before process launch. - snapshot/restore is optional because not all targets support filesystem or VM snapshots. - Future adapters should be added without changing session/runtime code. Planned backends: local, Docker, Azure Container Apps, Azure Container Instances, AKS, Databricks, E2B, Daytona, C... | | `SandboxBackend` | `type` | Public export. | | `SandboxCapabilities` | `type` | Public export. | | `SandboxEnv` | `type` | Public export. | | `SandboxExecOptions` | `type` | Public export. | | `SandboxFactory` | `type` | Public export. | | `SandboxFactoryOptions` | `type` | Public export. | | `SandboxFork` | `type` | Public export. | | `SandboxRef` | `type` | Public export. | | `SandboxRefDecoder` | `type` | Decoder for a `SerializedSandboxRef.provider`. Returns a SandboxFactory that, when invoked, produces a SandboxEnv connected to the existing remote sandbox identified by `providerData`. Decoders SHOULD attach without owning the remote sandbox's lifecycle — the returned env's `cleanup()` should detach, not destroy. | | `SandboxSnapshot` | `type` | Public export. | | `sanitizeObservabilityData` | `value` | Public export. | | `sanitizePublicJson` | `value` | Public export. | | `sanitizePublicText` | `value` | Remove credentials and host filesystem locations from caller-visible text. | | `schema` | `value` | Public export. | | `Schema` | `type` | Public export. | | `SchemaIssue` | `type` | Public export. | | `SchemaValidationError` | `value` | Public export. | | `SearchToolInput` | `type` | Public export. | | `SearchToolOptions` | `type` | Public export. | | `SearchToolResult` | `type` | Public export. | | `secret` | `value` | Public export. | | `SecretProvider` | `type` | Public export. | | `SecretRef` | `type` | Public export. | | `SecretResolutionContext` | `type` | Public export. | | `secretResolver` | `value` | Adapt a provider to the existing `init({ resolveSecret })` callback. | | `SerializedFabricError` | `type` | Public export. | | `SerializedSandboxRef` | `type` | Cross-process / cross-machine sandbox reference. Created by `session.sandboxRef({ portable: true })` and re-attached via `attachSandbox(serialized)` in a separate process. Each `provider` string maps to a decoder registered via `registerSandboxRefDecoder()`. | | `serializeFabricError` | `value` | Convert any thrown value into the stable public/developer transport shape. | | `serializeSandboxRef` | `value` | Serialize an in-process `SandboxRef` into the cross-process form. Requires the underlying sandbox to implement `encodeRef()`. Throws `SANDBOX_UNAVAILABLE` if the backend is in-process-only. | | `SessionData` | `type` | Public export. | | `SessionEntry` | `type` | Public export. | | `SessionEntryType` | `type` | Public export. | | `SessionHistory` | `value` | Public export. | | `SessionMemory` | `type` | Public export. | | `SessionMemoryEntry` | `type` | Persistent key/value store for facts an agent should remember across sessions — borrower preferences, prior outcomes, learned task history. Distinct from `SessionEntry` (which is the audit log): memory is for *recall*, entries are for *audit*. Memory writes do NOT land in the session log, so they don't pollute prompt context unless the agent explicitly reads them. Tenancy: every operation accepts an optional `tenantId`. Two tenants with the same `key` get isolated values. `tenantId` defaults to the empty string for non-tenant deployments. | | `SessionMemoryFilter` | `type` | Public export. | | `SessionMemoryGetOptions` | `type` | Public export. | | `SessionMemorySetInput` | `type` | Public export. | | `SessionOptions` | `type` | Public export. | | `SessionStore` | `type` | Public export. | | `setLogger` | `value` | Replace the global SDK logger. Call once at startup before any `init()`. Pass a custom Logger to redirect to your structured logging system. | | `ShellOptions` | `type` | Public export. | | `shellQuote` | `value` | Public export. | | `ShellResult` | `type` | Public export. | | `Skill` | `type` | Public export. | | `SkillOptions` | `type` | Public export. | | `slackApprovalNotifier` | `value` | Slack incoming-webhook notifier. The webhook URL remains in host configuration, never event data. | | `SnapshotPruneOptions` | `type` | Public export. | | `SnapshotPruneResult` | `type` | Public export. | | `StatInput` | `type` | Public export. | | `statTool` | `value` | Public export. | | `StdioMcpClient` | `value` | Public export. | | `StdioMcpClientOptions` | `type` | Public export. | | `StoredAttachment` | `type` | Public export. | | `StreamListenerRegistry` | `value` | Process-local listener registry shared by store implementations — registration, unsubscribe-and-prune, and error-swallowing notify. | | `SttEvent` | `type` | Public export. | | `SttProvider` | `type` | Public export. | | `SttSession` | `type` | Public export. | | `SttSessionOptions` | `type` | Public export. | | `SttSessionUsage` | `type` | Public export. | | `StubFabricAgent` | `value` | Public export. | | `StubFabricSession` | `value` | Public export. | | `SubmissionAbortedError` | `value` | Public export. | | `SubmissionAdmissionBackend` | `type` | Storage callbacks for `admitSubmissionWithBackend`. Every callback runs inside the transaction the caller has already opened (or the backend's equivalent atomicity scope). Callbacks may return plain values (synchronous backends) or native `Promise`s — non-native thenables are not supported. | | `SubmissionAdmissionRow` | `type` | The minimal shape `admitSubmissionWithBackend` needs from a persisted submission row: the transport `kind` and persisted `payload` it compares against the incoming admission. `payload` may be the serialized JSON string or an already-deserialized object (e.g. a Postgres JSONB column). | | `SubmissionAttemptRef` | `type` | Public export. | | `SubmissionClaimRef` | `type` | Public export. | | `SubmissionContext` | `type` | Public export. | | `SubmissionDurability` | `type` | Public export. | | `SubmissionExecuteOptions` | `type` | Public export. | | `SubmissionExecutor` | `type` | How the runner touches sessions. `execute` applies the submission's input to the addressed instance session and resolves with the turn result; everything else is store-level and must not require a live agent. Contract requirements: - `execute` must be idempotent by submission id (a resumed attempt whose input entry already exists must not append it again). - `recordTerminal` settles the conversation to a deterministic rest state (unresolved trailing tool calls get explicit interrupted-outcome markers — NEVER re-executed) and appends a terminal advisory. - `appendSettlement` appends the cano... | | `SubmissionInsertRow` | `type` | The queued row that `admitSubmissionWithBackend` writes on first admission. | | `SubmissionInspection` | `type` | Coarse persisted-progress classification consumed by reconciliation. | | `SubmissionInterruptedError` | `value` | Public export. | | `SubmissionInterruption` | `type` | Public export. | | `SubmissionPayloadContext` | `type` | Context needed for submission payload validation. Implementations extract these fields from their storage-specific row/document type before calling `isSubmissionPayload`. | | `SubmissionRetryExhaustedError` | `value` | Public export. | | `SubmissionRunner` | `type` | Public export. | | `SubmissionRunnerOptions` | `type` | Public export. | | `submissionSessionKey` | `value` | Store-session FIFO key of a submission (re-exported convenience). | | `SubmissionSettledRecord` | `type` | Minimal canonical settlement record for a direct submission. The conversation-stream phase reuses this shape as the durable terminal record a reconnecting waiter observes. | | `SubmissionSettlement` | `type` | Public export. | | `submissionSettlementEntryId` | `value` | Deterministic canonical settlement entry id for a submission. | | `SubmissionSettlementObligation` | `type` | Public export. | | `submissionStoreSessionId` | `value` | The harness identity string (`agent:<name>:<id>:<session>`) targeted by a submission input. This is the `persistentStoreSessionId` of the addressed instance session and the per-session FIFO key of the store. | | `SubmissionTelemetryEvent` | `type` | Public export. | | `SubmissionTelemetrySink` | `type` | Public export. | | `SubmissionTimeoutError` | `value` | Public export. | | `SuspendingSandboxEnv` | `value` | Decorator that adds idle-based auto-suspend to any `SandboxEnv` whose underlying implementation supports `suspend()` / `resume()`. If the inner env doesn't implement them, the decorator is a no-op pass-through (the idle timer never fires anything). The decorator tracks a `lastAccessAt` timestamp on every operation. After `idleSuspendMs` elapses without activity, it calls `inner.suspend()`. The next operation transparently calls `inner.resume()` first (unless `autoResumeOnAccess: false`). `cleanup()` cancels the idle timer. | | `SuspendingSandboxOptions` | `type` | Public export. | | `TaskOptions` | `type` | Public export. | | `TelemetryExporter` | `type` | Public export. | | `TelemetrySpan` | `type` | Public export. | | `tenantCostLimit` | `value` | Sugar over `CostLimit.perScope + scopeKey + store` for the common "per-tenant ceiling per period" pattern. Pick one of `perDayUsd`, `perHourUsd`, or `perMonthUsd`; when multiple are set, the most restrictive (smallest absolute) wins. Scope key convention: `tenant:<id>:<period>` where `<period>` is `day:YYYY-MM-DD`, `hour:YYYY-MM-DDTHH:00Z`, or `month:YYYY-MM`. Reset semantics (rollover) are the host's job — call `store.reset(scopeKey)` from a scheduled task to clear the period total. See the package declarations for an example. | | `TenantCostLimit` | `type` | Public export. | | `toFabricError` | `value` | Public export. | | `tokenBucketRateLimiter` | `value` | In-memory token-bucket rate limiter. Each key has its own bucket — keys are independent (waiting on one key doesn't block another). Buckets refill continuously at `tokensPerSecond`. | | `TokenBucketRateLimiterOptions` | `type` | Public export. | | `ToolCall` | `type` | Public export. | | `ToolCallResult` | `type` | Public export. | | `ToolContext` | `type` | Public export. | | `ToolDef` | `type` | Public export. | | `ToolEffect` | `type` | Public export. | | `ToolPolicy` | `type` | Public export. | | `toolsToModelSchemas` | `value` | Public export. | | `toOpenAIMessage` | `value` | Public export. | | `toOpenAITool` | `value` | Public export. | | `TtsProvider` | `type` | Public export. | | `TtsSynthesisOptions` | `type` | Public export. | | `TtsSynthesisUsage` | `type` | Public export. | | `TurnJournalState` | `type` | Public export. | | `UnimplementedSandboxEnv` | `value` | Public export. | | `unregisterSandbox` | `value` | Mark a registered sandbox as dead so future attach attempts fail. Called from the owner session's cleanup path. | | `unregisterSandboxRefDecoder` | `value` | Test/internal: remove a decoder. | | `validateResult` | `value` | Public export. | | `VERCEL_AI_GATEWAY_BASE_URL` | `value` | Default base URL for Vercel AI Gateway's OpenAI-compatible Chat Completions endpoint. The gateway accepts the standard OpenAI request body and routes through to the configured provider; switching from OpenAI to the gateway is just a base-URL change. See https://vercel.com/docs/ai-gateway/sdks-and-apis/openai-compat | | `vercelAIGateway` | `value` | Vercel AI Gateway model provider. The gateway is an OpenAI-compatible HTTP endpoint that brokers between your agent and any of the major model providers (OpenAI, Anthropic, Google, xAI, Groq, etc.) with a single key, observability, caching, and spend controls. Use this provider on any deploy target — Node, Cloudflare Workers, Vercel — to route inference through the gateway. See the package declarations for an example. Returns an `OpenAICompatibleModelProvider` configured for the gateway — use it anywhere a `ModelProvider` is accepted. Compatible with fabric-harness's tool-calling, retries,... | | `VercelAIGatewayProviderOptions` | `type` | Public export. | | `verifyAttachmentBytes` | `value` | Verify that `bytes` match the ref's digest (and declared size), throwing `AttachmentStoreError('DIGEST_MISMATCH')` otherwise. Every store's `put` MUST run this check before persisting. | | `verifyHmacSha256` | `value` | Constant-time HMAC-SHA256 verification (via `crypto.subtle.verify`). | | `VertexAIModelProvider` | `value` | Public export. | | `VertexAIProviderOptions` | `type` | Public export. | | `VirtualSandboxEnv` | `value` | Virtual sandbox backend powered by [just-bash](https://github.com/vercel-labs/just-bash). Provides an in-memory filesystem and a bash subset (`grep`, `glob`, `cat`, `read`, `mkdir`, `rm`, `ls`, `echo`, etc.) without shelling out to the host. The backend is fast, cheap, safe, and high-concurrency. Selected automatically by the bare `@fabric-harness/sdk` import when the caller doesn't pass `sandbox`. Override with `'local'`, `'docker'`, a `SandboxFactory`, or a `SandboxEnv` when you need real shell access. | | `VoiceAudioFormat` | `type` | Bidirectional voice / audio streaming surface. fabric-harness ships an OpenAI Realtime implementation; bring-your-own-vendor for Anthropic / Gemini Live / on-prem TTS+ASR pipelines. Audio frames flow in raw bytes — the standard format is PCM 16-bit little-endian at 24kHz mono (OpenAI Realtime default). Telephony bridges (Twilio Media Streams μ-law 8kHz, etc.) resample at the edge. Tool execution is the *caller's* responsibility: a `tool_call` event surfaces, the host code runs the tool through whatever governance gates apply (approvals, cost caps, rate limits), then calls `submitToolResult(... | | `VoiceConnectOptions` | `type` | Public export. | | `VoiceEvent` | `type` | Events streamed from a `VoiceSession`. `audio_delta` carries raw audio bytes; `text_delta` and `transcript` carry text; `tool_call` and `response_done` mark structured boundaries; `error` is fatal. | | `VoiceProvider` | `type` | Public export. | | `VoiceSession` | `type` | Public export. | | `VoiceToolResultInput` | `type` | Public export. | | `VoiceWsClientEvent` | `type` | Public export. | | `VoiceWsClientHandle` | `type` | Public export. | | `VoiceWsClientOptions` | `type` | Lightweight WebSocket client for the `fh server` `WS /sessions/:id/voice` endpoint. Server-side bridge owns the provider connection and API keys; this client just streams audio + control messages over WS. Works in browsers and on Node 22+ (uses the global `WebSocket`). | | `webhookApprovalNotifier` | `value` | Public export. | | `WebhookSubscriptionContext` | `type` | Generic webhook subscription primitive — wakes an agent on inbound events from any external system (event bus, queue, scheduler, third-party SaaS webhook, your own application's domain events). fabric-harness consumes a JSON payload and dispatches to the user-provided handler. The host event system decides which payloads land here; fabric-harness has no opinion on event taxonomy or producer. | | `WebhookSubscriptionDefinition` | `type` | Public export. | | `withConversationProjection` | `value` | Wrap a `SessionStore` so active-path appends are mirrored into an append-only `ConversationStreamStore` projection (v2 A4). The SessionEntry DAG stays the single source of truth; the stream gives clients offset-based catch-up + live tail. Two rules keep them coherent: 1. **Idempotent by entryId** — a crash between the DAG write and the stream write is repaired on the next append: the projector diffs the stream tail against the active path and re-emits anything missing. 2. **Truncation is explicit** — the DAG can branch (fork/replay/ checkpoint-restore rewrite `leafId`); the stream cannot. W... | | `withFilesystemSources` | `value` | Public export. | | `withIdleSuspend` | `value` | Wrap any `SandboxEnv` with idle-based auto-suspend. Returns the inner env unchanged when `idleSuspendMs` is undefined or the inner env doesn't implement `suspend()`. | | `WriteFileInput` | `type` | Public export. | | `writeFileTool` | `value` | Public export. | | `WsClientCommand` | `type` | Public export. | | `WsClientHandle` | `type` | Public export. | | `WsClientOptions` | `type` | Lightweight WebSocket client for the `fh server` `WS /sessions/:id/ws` endpoint. Works in browsers and on Node 22+ (uses the global `WebSocket`). Does NOT depend on the `ws` package — that's the server side's optional peer dep. | ### `@fabric-harness/sdk/cloudflare` | Export | Kind | Summary | | --- | --- | --- | | `CloudflareR2BucketLike` | `type` | Public export. | | `CloudflareR2ListResultLike` | `type` | Public export. | | `CloudflareR2ObjectBodyLike` | `type` | Public export. | | `r2FilesystemSource` | `value` | Read Cloudflare R2 objects as a Fabric filesystem source. Mount it with `withFilesystemSources()` (or `getVirtualSandbox(...)`) so agents can grep/read R2-backed knowledge bases through the normal sandbox tools. See the package declarations for an example. | | `R2FilesystemSourceOptions` | `type` | Public export. | ### `@fabric-harness/sdk/channel` | Export | Kind | Summary | | --- | --- | --- | | `bytesToHex` | `value` | Public export. | | `Channel` | `type` | Public export. | | `ChannelContext` | `type` | Public export. | | `ChannelDispatch` | `type` | Public export. | | `ChannelDispatchRequest` | `type` | Public export. | | `ChannelRoute` | `type` | Channels turn platform webhooks (Slack, GitHub, …) into agent dispatches. Handlers are written against the Web `Request`/`Response` API and `crypto.subtle`, so the same channel runs on Node and Cloudflare. A channel is a stateless route container plus a conversation-id (de)serializer — session continuity falls out of the key (same thread → same key → same session). | | `conversationKey` | `value` | Public export. | | `defineChannel` | `value` | Validates and brands a channel's routes. | | `defineTool` | `value` | Edge-safe identity helper equivalent to the root SDK's `defineTool`. | | `hexToBytes` | `value` | Public export. | | `hmacSha256` | `value` | Public export. | | `parseConversationKey` | `value` | Public export. | | `ParsedConversationKey` | `type` | Public export. | | `readJsonBody` | `value` | Reads the body once and returns the raw bytes, the decoded text, and the parsed JSON together — so a channel can HMAC-verify the exact bytes and use the JSON without re-reading the (already consumed) stream. Returns undefined only when the body exceeds `limitBytes`. | | `readRequestBody` | `value` | Reads the full request body as bytes, or returns undefined if it exceeds `limitBytes`. NOTE: this consumes the request stream (single read). Signature-verifying channels need the *exact* bytes for HMAC and the parsed JSON afterward — don't call `request.json()` as well. Use `readJsonBody` to get both from one read. | | `RequestBody` | `type` | Public export. | | `ToolDef` | `type` | Public export. | | `ToolEffect` | `type` | Public export. | | `verifyHmacSha256` | `value` | Constant-time HMAC-SHA256 verification (via `crypto.subtle.verify`). | ### `@fabric-harness/sdk/experimental` | Export | Kind | Summary | | --- | --- | --- | | `createPiAgentLoopRuntime` | `value` | Public export. | | `PiAgentLoopRuntime` | `value` | Public export. | | `PiAgentLoopRuntimeOptions` | `type` | Public export. | | `PiCustomModel` | `type` | Public export. | | `policiedSandboxEnv` | `value` | Wrap a `SandboxEnv` with `CapabilityPolicy` enforcement at the sandbox layer. Without this decorator, agent code that calls `(await session.sandbox).exec(...)` directly bypasses the policy that `session.shell()` and tool dispatch enforce. `policiedSandboxEnv()` closes that gap by re-running the same policy evaluation against `exec` / `readFile` / `writeFile` / `mkdir` / `rm` / etc. Throws `FabricError`: - `COMMAND_DENIED` when an exec is denied or requires approval. - `POLICY_DENIED` when a filesystem op is denied or requires approval. The decorator does NOT auto-resolve `requireApproval` p... | | `SuspendingSandboxEnv` | `value` | Decorator that adds idle-based auto-suspend to any `SandboxEnv` whose underlying implementation supports `suspend()` / `resume()`. If the inner env doesn't implement them, the decorator is a no-op pass-through (the idle timer never fires anything). The decorator tracks a `lastAccessAt` timestamp on every operation. After `idleSuspendMs` elapses without activity, it calls `inner.suspend()`. The next operation transparently calls `inner.resume()` first (unless `autoResumeOnAccess: false`). `cleanup()` cancels the idle timer. | | `SuspendingSandboxOptions` | `type` | Public export. | | `withIdleSuspend` | `value` | Wrap any `SandboxEnv` with idle-based auto-suspend. Returns the inner env unchanged when `idleSuspendMs` is undefined or the inner env doesn't implement `suspend()`. | ### `@fabric-harness/sdk/otel-observer` | Export | Kind | Summary | | --- | --- | --- | | `createOpenTelemetryObserver` | `value` | Public export. | | `OpenTelemetryObserverOptions` | `type` | Build a hierarchical OpenTelemetry trace from Fabric's event stream. Unlike `openTelemetryExporter` (which emits one flat span per duration-bearing event), this observer nests spans into a tree: `prompt`/`skill` operations contain `turn`s, which contain `tool` / `shell` spans, with `task` sub-work nested under the active operation. The result is a single parent trace per operation that visualizes the full agent run. This module lives on the `@fabric-harness/sdk/otel-observer` subpath (not the main entry) because building parent contexts requires `@opentelemetry/api` at runtime; the main SDK... | ### `@fabric-harness/sdk/testing` | Export | Kind | Summary | | --- | --- | --- | | `MockModelProvider` | `value` | Public export. | | `StubFabricAgent` | `value` | Public export. | | `StubFabricSession` | `value` | Public export. | ### `@fabric-harness/sdk/testing/contracts` | Export | Kind | Summary | | --- | --- | --- | | `AttachmentStoreContractHandle` | `type` | Public export. | | `ChannelContractDispatch` | `type` | Public export. | | `ChannelContractFixture` | `type` | Public export. | | `ChannelContractRequest` | `type` | Public export. | | `ConversationStreamStoreContractHandle` | `type` | Public export. | | `defineAttachmentStoreContractTests` | `value` | Register the standard `AttachmentStore` contract tests under the given describe label. Each test gets a fresh store from `factory()`. | | `defineChannelContractTests` | `value` | Shared behavioral contract for first-party and community channel adapters. | | `defineConversationStreamStoreContractTests` | `value` | Register the standard `ConversationStreamStore` contract tests under the given describe label. Each test gets a fresh store from `factory()`. | | `definePersistenceBundleContractTests` | `value` | Compose all store contracts with bundle health, cost, run, and cascade checks. | | `defineSubmissionStoreContractTests` | `value` | Register the standard `AgentSubmissionStore` contract tests under the given describe label. Each test gets a fresh store from `factory()`. | | `PersistenceBundleContractHandle` | `type` | Public export. | | `SubmissionStoreContractHandle` | `type` | Public export. | ## @fabric-harness/temporal ### `@fabric-harness/temporal` | Export | Kind | Summary | | --- | --- | --- | | `ActivityIdempotency` | `type` | Public export. | | `ActivityTimeoutPolicy` | `type` | Public export. | | `adaptSessionRuntime` | `value` | Adapt any `SessionRuntime` (Temporal-backed, Mock, or otherwise) into the SDK's `DurableSessionRuntime` shape. The two interfaces are structurally identical, but the type bridging keeps SDK types out of the temporal package's public API. | | `AppendSessionEntryActivityInput` | `type` | Public export. | | `AppendSessionEntryActivityResult` | `type` | Public export. | | `AppendSessionEventActivityInput` | `type` | Public export. | | `APPROVAL_SIGNAL` | `value` | Public export. | | `approvalSignal` | `value` | Public export. | | `ApprovalSignal` | `type` | Public export. | | `BuildContextActivityInput` | `type` | Public export. | | `BuildContextActivityResult` | `type` | Public export. | | `CHECKPOINT_CREATE_WORKFLOW_NAME` | `value` | Public export. | | `CHECKPOINT_RESTORE_WORKFLOW_NAME` | `value` | Public export. | | `CheckpointActivityInput` | `type` | Public export. | | `CheckpointActivityResult` | `type` | Public export. | | `checkpointCreateWorkflow` | `value` | Public export. | | `checkpointRestoreWorkflow` | `value` | Public export. | | `CompactSessionActivityInput` | `type` | Public export. | | `CompactSessionActivityResult` | `type` | Public export. | | `connectTemporalWithRetry` | `value` | Public export. | | `createInlineSessionRuntime` | `value` | Public export. | | `createLocalTemporalActivities` | `value` | Public export. | | `createMockTemporalRuntime` | `value` | Public export. | | `createTemporalClient` | `value` | Public export. | | `createTemporalClientConnectionOptions` | `value` | Public export. | | `createTemporalDispatchActivities` | `value` | Wrap a `DispatchProcessor` as a Temporal activity. The processor is idempotent by `dispatchId`, so Temporal retries are safe. | | `CreateTemporalDispatchActivitiesOptions` | `type` | Public export. | | `createTemporalSessionRuntimeActivities` | `value` | Public export. | | `CreateTemporalSessionRuntimeActivitiesOptions` | `type` | Public export. | | `createTemporalWorkerConnectionOptions` | `value` | Public export. | | `CUSTOM_APPROVAL_WORKFLOW_NAME` | `value` | Public export. | | `customApprovalWorkflow` | `value` | Durable custom approval gate used by `session.approval.request()`. | | `CustomApprovalWorkflowInput` | `type` | Public export. | | `DEFAULT_TEMPORAL_ACTIVITY_TIMEOUTS` | `value` | Public export. | | `DEFAULT_TEMPORAL_ADDRESS` | `value` | Default Temporal frontend address used by CLI / build scaffolds when nothing is configured. | | `DEFAULT_TEMPORAL_NAMESPACE` | `value` | Default namespace. | | `DEFAULT_TEMPORAL_TASK_QUEUE` | `value` | Default task queue name. | | `defineTemporalAgent` | `value` | Public export. | | `DefineTemporalAgentOptions` | `type` | Public export. | | `DISPATCH_WORKFLOW_NAME` | `value` | Public export. | | `dispatchWorkflow` | `value` | Durable dispatch: applies one dispatched input to a persistent instance via the `processDispatch` activity. The activity is idempotent by `dispatchId`, so Temporal retries (and worker restarts) won't double-apply. | | `ExecuteToolActivityInput` | `type` | Public export. | | `ExecuteToolActivityResult` | `type` | Public export. | | `HYBRID_PROMPT_WORKFLOW_NAME` | `value` | Public export. | | `hybridPromptWorkflow` | `value` | Hybrid durable prompt workflow. The workflow owns deterministic turn orchestration while activities perform nondeterministic effects: context construction, model calls, tool execution, approval policy checks, and durable entry/event appends. | | `InlineSessionRuntime` | `value` | Public export. | | `LoadSessionActivityInput` | `type` | Public export. | | `LocalTemporalActivitiesOptions` | `type` | Public export. | | `MockTemporalClient` | `value` | Minimal mock Temporal client that satisfies enough of the TemporalClientHandle interface for agent tests. Delegates SessionRuntime calls to MockTemporalRuntime. | | `MockTemporalModelProvider` | `value` | Minimal mock model provider for Temporal agent tests. Returns deterministic responses based on the latest user message. | | `MockTemporalRuntime` | `value` | Public export. | | `MockTemporalRuntimeOptions` | `type` | Public export. | | `ModelGenerateActivityInput` | `type` | Public export. | | `ModelGenerateActivityResult` | `type` | Public export. | | `PendingApprovalState` | `type` | Public export. | | `PROMPT_WORKFLOW_NAME` | `value` | Public export. | | `promptWorkflow` | `value` | Coarse one-shot prompt workflow. Useful as a compatibility path while the hybrid loop is still gaining lower-level activity implementations. | | `PromptWorkflowInput` | `type` | Public export. | | `PromptWorkflowResult` | `type` | Public export. | | `requireIdempotency` | `value` | Public export. | | `ResolvedTemporalConnectionConfig` | `type` | Public export. | | `ResolveSecretActivityInput` | `type` | Public export. | | `resolveTemporalConnectionConfig` | `value` | Public export. | | `resolveToolRefs` | `value` | Public export. | | `RuntimeCheckpointCreateActivityInput` | `type` | Public export. | | `RuntimeCheckpointRestoreActivityInput` | `type` | Public export. | | `RuntimePromptActivityInput` | `type` | Public export. | | `RuntimeShellActivityInput` | `type` | Public export. | | `SESSION_STATE_QUERY` | `value` | Public export. | | `SessionRuntime` | `type` | Public export. | | `SessionRuntimeApprovalInput` | `type` | Public export. | | `SessionRuntimeCheckpointCreateInput` | `type` | Public export. | | `SessionRuntimeCheckpointRestoreInput` | `type` | Public export. | | `SessionRuntimeFactory` | `type` | Public export. | | `SessionRuntimePromptInput` | `type` | Public export. | | `SessionRuntimeShellInput` | `type` | Public export. | | `SessionRuntimeTaskInput` | `type` | Public export. | | `sessionStateQuery` | `value` | Public export. | | `sessionWorkflow` | `value` | Long-lived session coordination workflow. This first production Temporal integration supports approval signaling and state queries. Prompt/shell/checkpoint operations are workflows below. | | `SessionWorkflowDefinition` | `type` | Public export. | | `SHELL_WORKFLOW_NAME` | `value` | Public export. | | `shellWorkflow` | `value` | Public export. | | `SnapshotRef` | `type` | Public export. | | `startTemporalWorker` | `value` | Public export. | | `TASK_WORKFLOW_NAME` | `value` | Public export. | | `taskWorkflow` | `value` | Public export. | | `TaskWorkflowInput` | `type` | Public export. | | `temporal` | `value` | Public export. | | `TemporalActivities` | `type` | Public export. | | `TemporalActivityTimeouts` | `type` | Public export. | | `temporalAgent` | `value` | Public export. | | `TemporalAgentOptions` | `type` | Public export. | | `TemporalBundle` | `type` | Public export. | | `TemporalBundleConfig` | `type` | Public export. | | `TemporalClientHandle` | `type` | Public export. | | `TemporalClientOptions` | `type` | Public export. | | `TemporalConnectionConfig` | `type` | Public export. | | `TemporalConnectRetryOptions` | `type` | Public export. | | `TemporalDispatchActivities` | `type` | Activity that durably applies a dispatched input to a persistent instance. | | `TemporalDispatchClientLike` | `type` | Minimal Temporal client surface needed to start a dispatch workflow. | | `temporalDispatchQueue` | `value` | Durable dispatch queue. `enqueue` starts a `dispatchWorkflow` keyed by the `dispatchId`, so delivery survives worker/process restarts and a duplicate `dispatchId` maps to the same workflow id (idempotent admission). Implements the SDK's `DispatchQueue`; pair with `createTemporalDispatchActivities` on the worker. When `runtime: 'temporal'` is configured, this is the default dispatch queue (D4) unless an explicit queue is supplied. | | `TemporalDispatchQueueOptions` | `type` | Public export. | | `TemporalIntegrationMode` | `type` | Public export. | | `TemporalPromptWorkflowMode` | `type` | Public export. | | `TemporalRunStatus` | `type` | Public export. | | `TemporalRuntimeOptions` | `type` | Public export. | | `temporalSessionRuntime` | `value` | Build a `DurableSessionRuntimeFactory` for `init({ sessionRuntime })` that delegates each session's `prompt`/`task`/`shell`/`checkpoint` calls to a Temporal workflow. The Temporal client connection is opened lazily on the first session and shared across all sessions produced by this factory. Pair with `runtime: 'temporal'` for production deployments. For tests, pass `mode: 'mock'` to use the in-process mock runtime. See the package declarations for an example. | | `TemporalSessionRuntime` | `value` | Public export. | | `TemporalSessionRuntimeActivities` | `type` | Public export. | | `TemporalSessionWorkflowInput` | `type` | Public export. | | `TemporalSignalClient` | `type` | Public export. | | `TemporalTlsConfig` | `type` | Public export. | | `TemporalWorkerHandle` | `type` | Public export. | | `TemporalWorkerOptions` | `type` | Public export. | | `TemporalWorkflowQueryState` | `type` | Public export. | | `ToolApprovalRequest` | `type` | Public export. | ### `@fabric-harness/temporal/agent` | Export | Kind | Summary | | --- | --- | --- | | `defineTemporalAgent` | `value` | Public export. | | `DefineTemporalAgentOptions` | `type` | Public export. | | `resolveToolRefs` | `value` | Public export. | | `temporal` | `value` | Public export. | | `temporalAgent` | `value` | Public export. | | `TemporalAgentOptions` | `type` | Public export. | | `TemporalBundle` | `type` | Public export. | | `TemporalBundleConfig` | `type` | Public export. | --- # Build Manifest Canonical: https://harness.fabric.pro/docs/reference/build-manifest Schema-v2 manifest emitted by fh build for jobs, persistent agents, files, and provenance. Every `fh build` writes `manifest.json` at the artifact root. Schema v2 separates finite jobs from persistent agents and records every emitted file for verification. ## Current shape ```jsonc { "schemaVersion": 2, "createdAt": "2026-07-09T21:59:56.279Z", "target": "databricks-app", "workspaceRoot": "/source/analytics-agent", "outDir": "/source/analytics-agent/.fabricharness/build/databricks-app", "entrypoint": "dist/server.mjs", "agentsManifestSha256": "46df8f...", "config": { "source": ".fabricharness/config.ts", "agent": { "model": "databricks/analytics-model" }, "sandbox": "local" }, "jobs": [ { "name": "analyst", "kind": "job", "source": ".fabricharness/jobs/analyst.mjs", "entry": "jobs/analyst.mjs", "sha256": "68d820...", "triggers": { "webhook": true, "manual": true } } ], "agents": [ { "name": "copilot", "kind": "agent", "source": ".fabricharness/agents/copilot.mjs", "entry": "agents/copilot.mjs", "sha256": "bba012..." } ], "roles": [ { "name": "data-analyst", "path": ".fabricharness/roles/data-analyst.md", "description": "Analyze governed data." } ], "skills": [ { "name": "analyze-table", "path": ".fabricharness/skills/analyze-table/SKILL.md" } ], "files": [ { "path": "dist/server.mjs", "bytes": 3874732, "sha256": "b5b8ab..." } ], "package": { "name": "analytics-agent", "version": "1.0.0", "type": "module", "dependencies": {} } } ``` `agentsManifestSha256` is retained as the compatibility field name, but its digest covers both `jobs` and `agents` in schema v2. ## Inspect a build ```sh jq '{schemaVersion, target, jobs, agents, entrypoint}' \ .fabricharness/build/node/manifest.json ``` List every local build: ```sh fh builds ``` The Node admin API can read manifests that remain under the active workspace build directory: ```sh curl http://localhost:4317/builds/node/manifest ``` For a standalone artifact deployed elsewhere, read `manifest.json` from the artifact filesystem or publish it through your deployment platform. The shared v2 server does not expose a root `GET /manifest` route. ## Schema-v1 compatibility `readBuildManifest()` normalizes legacy manifests by treating the old `agents` collection as finite jobs and returning schema-v2 data: ```ts import { readBuildManifest } from '@fabric-harness/node'; const manifest = await readBuildManifest(process.cwd(), 'node'); for (const job of manifest?.jobs ?? []) { console.log('job', job.name); } for (const agent of manifest?.agents ?? []) { console.log('persistent agent', agent.name); } ``` External manifest consumers should migrate explicitly rather than assuming every definition is an agent. ## Supply-chain verification Build with metadata: ```sh fh build --target docker \ --provenance \ --attestation \ --sbom ``` Then verify: ```sh fh verify-provenance .fabricharness/build/docker fh verify-attestation .fabricharness/build/docker ``` Provenance and attestations include definition name, kind, source, and digest. The `files` list lets deployment tooling independently verify the emitted tree. ## See also - [Build and run artifacts](/docs/deployment/build-artifacts) - [Node deployment](/docs/deployment/node) - [Security hardening](/docs/reference/security-hardening) --- # Capability Matrix Canonical: https://harness.fabric.pro/docs/reference/capability-matrix Runtime behavior and deployment requirements by feature and target. Use this page to choose a runtime, sandbox, integration, and validation path. Hosted services require credentials and permissions in the account where the agent will run. ## Core runtime | Capability | Runtime behavior | Deployment requirement | | --- | --- | --- | | Finite jobs in `.fabricharness/jobs/` | `job({ run })` / `agent({ run })`, typed input/output, `POST /jobs/:name`. | Configure authentication and request limits on public servers. | | Persistent agents | Durable `202` submissions, FIFO execution, offset streams, abort, and WebSocket. | Select a durable session, submission, and stream store. | | Durable state and attachments | In-memory, file, SQLite, Postgres, and UC Volumes implementations. | Configure backup, retention, deletion, and recovery objectives. | | `@fabric-harness/client` | Send, wait, status, history, observe, abort, and job invocation. | Supply the server token or application authentication headers. | | Node runtime | Shared HTTP server for development and Node-derived build targets. | Configure auth, rate limits, durable stores, backup, and monitoring. | | Enterprise principal/RBAC/deletion | Tenant-bound principals, scoped permissions, actor propagation, and cascade deletion. | Map the application identity provider to explicit Fabric permissions. | | MCP server exposure | Authenticated Streamable HTTP for jobs, persistent agents, and custom tools. | Restrict exposed tools and authenticate every remote client. | | Cron scheduler | Timezone-aware schedules, overlap prevention, payload resolver, graceful shutdown. | Run one scheduler leader or provide deployment-level leader election. | | Operator console | Session and tenant inspection, approval count, abort, and deletion at `/admin`. | Grant only `admin:read`, `session:abort`, and `session:delete` as needed. | | `fh build` | Schema-v2 manifests and shared-server artifacts for jobs and persistent agents. | Verify the artifact attestation and environment configuration before deploy. | | Temporal runtime | Durable prompt, skill, child task, shell, checkpoint, tool/custom approval, cancellation, activity retry, attachment, and usage/cost paths. | Run the credentialed conformance suite against the deployment namespace and validate worker restart. | | Policies, approvals, audit, cost budgets | Definition controls combine with invocation controls as enforcement floors. | Route mutating operations through explicit policy and approval rules. | | Tools, skills, roles, tasks, MCP | Built-in tools, Markdown skills, role overlays, nested tasks, and remote MCP clients. | Validate remote MCP authentication and tool allowlists. | | Evals and telemetry | Built-in scorers, OpenTelemetry, Langfuse, cost, and session metrics. | Configure the chosen telemetry sink and its retention policy. | ## Sandboxes and targets | Backend or target | Runtime behavior | Deployment requirement | | --- | --- | --- | | Virtual sandbox | Fast in-memory filesystem and shell subset. | Use when process isolation is unnecessary. | | Local sandbox | Host filesystem and process execution. | Restrict to trusted code and scoped working directories. | | Docker sandbox | Per-session container filesystem and commands. | Verify the selected image, resource limits, network policy, and cleanup. | | Cloudflare | Durable finite runs and persistent submissions with FIFO leases, restart reconciliation, offset streams, attachments, abort, and deletion using Workers AI, Durable Objects, R2, Sandbox, and Shell Workspace. | Configure bindings and run the workerd plus account smoke commands from the deployment guide. | | Azure Container Instances | ACR build and managed-identity deployment automation. | Configure subscription, identity, registry, network, and resource-group access. | | Daytona / E2B / Modal | Remote sandbox adapters for provider-managed execution. | Pin the provider SDK and verify lifecycle, timeout, file, and cleanup behavior. | | Kubernetes / AKS | Pod-backed sandbox execution. | Configure cluster identity, namespace policy, quotas, networking, and pod cleanup. | | Azure / Foundry | AKS sandbox, Azure targets, managed-identity model provider, and Foundry Agent Service helpers. | Validate tenant identity, role assignments, networking, and regional service availability. | ## Databricks | Capability | Runtime behavior | Deployment requirement | | --- | --- | --- | | Databricks inference provider | Unity AI Gateway at `/ai-gateway/mlflow/v1` for `system.ai.*`; explicit custom endpoints retain `/serving-endpoints`. Both use rotating bearer credentials. | Grant access to the selected model service or custom endpoint. | | PAT, OAuth M2M, Apps identity, and OBO | Service and user identity helpers with token refresh. | Configure the required scopes and enable Databricks user authorization when using OBO. | | SQL and Unity Catalog tools | Governed discovery and statement execution; Unity Catalog remains authoritative. | Grant catalog/schema/object access and require approval for arbitrary SQL. | | Vector Search, embeddings, AI Functions, and Genie | Retrieval, embedding, warehouse inference, and conversational analytics tools. | Grant endpoint, index, warehouse, and Genie space access. | | Lakeflow and Feature Serving | Read and execute tools with effect metadata for policy routing. | Grant job, pipeline, and endpoint permissions to the runtime principal. | | Consumption and cost reconciliation | System Tables reads for delayed actual-cost reconciliation. | Grant System Tables access and retain estimated budgets for real-time enforcement. | | Databricks Apps build/deploy | Shared-server artifact with separate jobs and persistent agents. | Configure App resources, environment variables, identity, and health probes. | | Model Serving build/deploy | MLflow `ChatAgent` proxy to an externally hosted Fabric agent. | Deploy the TypeScript runtime separately and secure the proxy-to-runtime connection. | | Lakebase persistence | OAuth database credential exchange plus session, submission, and stream stores. | Configure endpoint coordinates, database grants, migrations, backup, and restart recovery. | | Databricks SQL sandbox | `exec()` uses SQL Statement Execution; filesystem operations remain in memory. | Select a warehouse and Unity Catalog scope appropriate for the agent. | | Certification evidence | Secret-redacted JSON evidence for configured workspace capabilities. | Run `fh databricks certify` with the services required by the deployment. | ## Channels and databases | Capability | Runtime behavior | Deployment requirement | | --- | --- | --- | | Slack, GitHub, Discord, Teams, Telegram, Twilio, WhatsApp | Authentication, event normalization, identity propagation, deduplication, and governed replies. | Configure provider credentials, webhook verification, retry handling, and application installation. | | Postgres, MySQL, SQLite, MongoDB, Redis data tools | Fixed SQL operations, host-built filters, namespaced keys, limits, effects, and redacted failures. | Use least-privilege database credentials and apply tenant scoping to every operation. | ## Databricks deployment validation Run the Databricks certification workflow in the target workspace before rollout. Include identity, restart recovery, Unity Catalog denial cases, tool approvals, retry behavior, and cost attribution in the deployment record. --- # Context Compaction Canonical: https://harness.fabric.pro/docs/reference/compaction Automatic, event-emitting context compaction for long sessions. Default import enables it; /strict opts in explicitly. Threshold and overflow modes. import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; When a session's message history grows past the model's context window, an agent will start failing or thrashing. Fabric Harness solves this with automatic compaction: older session entries are summarized into a single compact entry, recent entries are kept verbatim, and the agent keeps running. ## Defaults | SDK | Default | Why | |---|---|---| | **Default import** (`@fabric-harness/sdk`) | `enabled: true` | Headless agents are often webhook/serverless handlers. Failing at the context window is the wrong default. | | **Strict import** (`@fabric-harness/sdk/strict`) | not injected — `undefined` until you set it | Required for Temporal replay determinism: auto-compaction is non-deterministic across replays. Opt in only when you don't need replay safety. | > ⚠️ Selecting `runtime: 'temporal'` from the default import emits a one-time `console.warn` because of this exact reason. Either switch to `/strict` or pass `compaction: { enabled: false }` explicitly. ## Trigger modes 1. **Threshold** — before each model call, the harness estimates token usage from session history. When estimated tokens exceed `compactAtTokens` (auto-derived as `contextWindowTokens - reserveTokens` if not set), it compacts before sending the next prompt. 2. **Overflow** — if the model returns a `context overflow` error anyway, the harness compacts and retries the prompt once. Set `recoverFromOverflow: false` to disable. Both paths emit a typed `compaction` [event](/docs/reference/events) with `reason: 'threshold' | 'overflow'`, `messagesBefore`, `messagesAfter`, and `tokensBefore`. ## Configuration ```ts import { agent } from '@fabric-harness/sdk'; export default agent<{ message: string }>({ name: 'long-running', run: async ({ init, input }) => { // Compaction is on by default. Override only if you need to. const session = await (await init({ compaction: { enabled: true, reserveTokens: 8192, // default 4096 keepRecentEntries: 30, // default 20 // compactAtTokens auto-derived from contextWindow if not set }, })).session(); return { reply: await session.prompt(input.message) }; }, }); ``` To disable: `init({ compaction: { enabled: false } })`. ```ts import { agent, schema } from '@fabric-harness/sdk/strict'; export default agent({ name: 'long-running', input: schema.object({ message: schema.string() }), run: async ({ init, input }) => { // Off by default in /strict — opt in explicitly. Avoid for Temporal. const session = await (await init({ runtime: 'inline', sandbox: 'local', compaction: { enabled: true, reserveTokens: 16_384, keepRecentEntries: 30, recoverFromOverflow: true, }, })).session(); return await session.prompt(input.message); }, }); ``` ## `CompactionSettings` reference | Field | Type | Default | Purpose | |---|---|---|---| | `enabled` | `boolean` | Default import: `true`; `/strict`: not injected | Master switch. | | `compactAtTokens` | `number` | derived from `contextWindowTokens - reserveTokens` | Trigger threshold. | | `reserveTokens` | `number` | `4096` | Headroom kept below the model's context window. | | `keepRecentEntries` | `number` | `20` | Recent entries kept verbatim during compaction. Older entries get folded into the summary. | | `recoverFromOverflow` | `boolean` | `true` | Catch provider context-overflow errors, compact, and retry once. | All fields can also be set per-prompt via `session.prompt(text, { compactAtTokens, reserveTokens, compactionKeepRecentEntries, recoverContextOverflow })`. Per-prompt values win. ## Subscribing to compaction events ```ts import { init, isEvent, type AgentEvent } from '@fabric-harness/sdk'; const fabric = await init({ onEvent: (event: AgentEvent) => { if (isEvent(event, 'compaction')) { console.log( `compacted ${event.data.messagesBefore} → ${event.data.messagesAfter}`, `(reason: ${event.data.reason}, ${event.data.tokensBefore} tokens)`, ); } }, }); ``` See the [Events reference](/docs/reference/events) for the full event taxonomy. ## Manual compaction You can also force compaction outside the auto-loop: ```ts const result = await session.compact({ keepRecentEntries: 10, generateSummary: true, reason: 'manual', }); console.log(result.summary, result.compactedEntries); ``` ## How the summary is generated The summary is produced by the configured model with a deterministic fallback when no model is available. Compaction never breaks an active tool-call/tool-result pair — those move atomically into the kept-recent window. References to files read or modified are appended as a separate block so the agent can re-discover them after compaction. The summary entry replaces the compacted slice in the session history; subsequent prompts see only the summary plus the recent entries you chose to keep. ## When *not* to enable compaction - Short prompts that always fit (one-shot classifiers). - Agents that need an exact, replayable conversation history (legal/audit). Use Temporal-backed durability instead. - Tasks where summary loss could change the outcome (e.g., the agent must remember a specific identifier mentioned 50 turns ago). In that case, persist key facts as artifacts and reference them by name. ## See also - [Events](/docs/reference/events) — typed `compaction` event payload - [Sessions and prompts](/docs/building/sessions-prompts) — where `prompt()` evaluates the budget - [Telemetry](/docs/reference/telemetry) — exporting compaction metrics --- # Errors Canonical: https://harness.fabric.pro/docs/reference/errors Handle stable Fabric error codes without exposing secrets or host paths. Fabric errors have a stable `code`, retryability, and optional structured details. Match codes, not message text: ```ts import { FabricError } from '@fabric-harness/sdk'; try { await session.prompt('Run the report'); } catch (error) { if (error instanceof FabricError && error.code === 'MODEL_RATE_LIMITED') { await retryLater(); } throw error; } ``` Use `serializeFabricError(error, 'public')` at an HTTP or queue boundary. Public serialization uses the safe message and public details, removes filesystem paths, and redacts authorization values, tokens, API keys, passwords, credentials, and known provider-key formats. Developer serialization retains diagnostic context but still redacts secrets. ```ts import { serializeFabricError } from '@fabric-harness/sdk'; return Response.json( { error: serializeFabricError(error, 'public') }, { status: 500 }, ); ``` The Node package exports two specialized subclasses: | Class | Stable codes | | --- | --- | | `FabricBuildError` | `BUILD_FAILED`, `BUILD_INVALID_TARGET`, `BUILD_VERIFICATION_FAILED` | | `FabricPersistenceError` | `PERSISTENCE_CONFLICT`, `PERSISTENCE_UNAVAILABLE`, `PERSISTENCE_INVALID_CONFIG`, `SESSION_NOT_FOUND` | `PERSISTENCE_CONFLICT` is retryable when an ownership lease or optimistic version loses a race. Configuration and verification errors are not retryable until the caller changes input or the artifact. `@fabric-harness/client` preserves server `code`, `details`, and `retryable` on `FabricClientError`, so browser and service clients can use the same branching logic. --- # Eval Library Canonical: https://harness.fabric.pro/docs/reference/eval-library API reference for @fabric-harness/evals. Install `@fabric-harness/evals` and import all APIs from the package root. ## Suite API ```ts import { defineEvalSuite, runEvalSuite } from '@fabric-harness/evals'; ``` - `defineEvalSuite(suite)` preserves generic inference and returns the suite unchanged. - `runEvalSuite(suite)` runs cases sequentially and returns `EvalSuiteResult`. - `EvalSuite` contains `name`, `cases`, `runner`, `scorers`, optional `passThreshold`, and optional metadata. - `EvalCase` contains `id`, `input`, optional `expected`, tags, and metadata. - `EvalScore` contains `name`, a `0..1` score, optional `passed`, reason, and metadata. A case passes when no scorer explicitly returns `passed: false` and its average score meets `passThreshold` (default `0.8`). A runner or scorer error records a failed case instead of aborting the remaining suite. ## Built-in scorers ```ts exactMatchScorer(name?) containsTextScorer(expectedText?, name?) regexMatchScorer(pattern, { name?, flags? }) jsonShapeMatchScorer(shape, { name? }) llmAsJudgeScorer({ judge, model, rubric, name?, passThreshold? }) ``` `containsTextScorer()` uses the case's string `expected` value when no fixed text is provided. `jsonShapeMatchScorer()` checks required keys and primitive types while allowing extra keys. `llmAsJudgeScorer()` expects a model provider with `generate()` and treats malformed judge output as a zero score. ## Custom scorer ```ts import type { EvalScorer } from '@fabric-harness/evals'; const concise: EvalScorer = ({ output }) => ({ name: 'concise', score: output.length <= 200 ? 1 : 0, passed: output.length <= 200, reason: `${output.length} characters`, }); ``` See [Evaluations](/docs/building/evals) for job execution and CLI examples. --- # Agent Events Canonical: https://harness.fabric.pro/docs/reference/events Subscribe to a typed event stream from any Fabric Harness agent — text deltas, tool calls, shell commands, compaction, approvals, tasks, errors. Available from both import entrypoints. import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Every Fabric Harness agent emits a structured event stream as it runs. The same callback shape is accepted at every level — `init({ onEvent })`, `agent.session(id, { onEvent })`, and `session.prompt(text, { onEvent })` — and propagates downward, so wiring once at `init()` is usually enough. ## Quick start ```ts import { agent, isEvent, type AgentEvent } from '@fabric-harness/sdk'; export default agent<{ message: string }>({ name: 'echo', run: async ({ init, input }) => { const fabric = await init({ onEvent: (event: AgentEvent) => { if (isEvent(event, 'text_delta')) process.stdout.write(event.data.delta); if (isEvent(event, 'tool_start')) console.error(`> ${event.data.toolName}`); if (isEvent(event, 'compaction')) console.error(`compacted ${event.data.messagesBefore} → ${event.data.messagesAfter}`); }, }); const session = await fabric.session(); return { reply: await session.prompt(input.message) }; }, }); ``` ```ts import { agent, isEvent, schema, type AgentEvent } from '@fabric-harness/sdk'; export default agent({ name: 'echo', input: schema.object({ message: schema.string() }), output: schema.string(), run: async ({ init, input }) => { const fabric = await init({ onEvent: (event: AgentEvent) => { if (isEvent(event, 'tool_end') && event.data.isError) { console.error(`tool ${event.data.toolName} failed`, event.data.result); } if (isEvent(event, 'approval_requested')) { console.error(`approval needed for ${event.data.subject}`); } }, }); const session = await fabric.session(); return await session.prompt(input.message); }, }); ``` ## Event types All events share a common envelope: ```ts interface AgentEventBase { id: string; // stable id for this occurrence timestamp: string; // ISO-8601 sessionId?: string; parentSessionId?: string; // set on child task events taskId?: string; // set on child task events } ``` The `type` discriminator narrows `data` automatically: | Type | Data shape | When it fires | |---|---|---| | `agent_start` | — | `init()` resolves | | `session_start` | `{ id?: string }` | `agent.session()` resolves | | `prompt_start` | `{ text?: string }` | start of `session.prompt()` | | `prompt_end` | `{ text?: string; result?: unknown }` | end of `session.prompt()` | | `turn_start` / `turn_end` | `{ stopReason?: string }` | each model turn inside the harness loop | | `model_attempt` | provider-shaped | each model call (incl. retries) | | `text_delta` | `{ delta: string }` | streaming text from the model | | `tool_start` | `{ toolName, toolCallId?, args? }` | a tool call begins | | `tool_end` | `{ toolName, toolCallId?, isError?, result? }` | a tool call resolves | | `command_start` | `{ command: string; cwd? }` | `session.shell()` or scoped command begins | | `command_end` | `{ command: string; exitCode: number; durationMs? }` | shell command resolves | | `skill_start` / `skill_end` | `{ name, result? }` | `session.skill()` lifecycle | | `task_start` | `{ taskId, depth }` | `session.task()` spawns a child agent | | `task_end` | `{ taskId, depth, durationMs? }` | child task resolves | | `task_failed` / `task_cancelled` | `{ taskId, error? }` | child task fails or is cancelled | | `task_checkpoint` | `{ taskId }` | durable child checkpoint persisted | | `compaction` | `{ reason?: 'threshold' \| 'overflow'; messagesBefore; messagesAfter; tokensBefore?; tokensAfter? }` | context compaction runs (when enabled) | | `artifact_created` | `{ path; contentType? }` | `session.artifact()` persists | | `checkpoint_created` / `checkpoint_restored` | `{ id }` | session checkpoints | | `approval_requested` | `{ approvalId; subject; kind; risk? }` | a `requireApproval` policy match needs human ack | | `approval_voted` | `{ approvalId; outcome }` | someone voted via `fh approvals approve/deny` | | `approval_granted` / `approval_denied` / `approval_expired` | `{ approvalId }` | approval terminal states | | `metric` | `JsonObject` | counter/gauge/histogram update | | `result` | `{ usage? }` | final result emitted | | `result_retry` | provider-shaped | typed-result schema mismatch caused a retry | | `error` | `{ error: string; cause? }` | thrown by anywhere in the agent loop | ## Type-safe access with `isEvent` ```ts import { isEvent } from '@fabric-harness/sdk'; onEvent: (event) => { if (isEvent(event, 'tool_start')) { // event.data is { toolName: string; toolCallId?: string; args?: JsonObject } console.log(event.data.toolName, event.data.args); } } ``` Or use a `switch` on `event.type` — TS narrows the same way. ## Streaming via `session.stream()` The same events can be consumed as an async generator instead of a callback: ```ts import type { AgentEvent } from '@fabric-harness/sdk'; for await (const event of session.stream('Triage this issue')) { // event: AgentEvent — same union, same narrowing if (event.type === 'text_delta') process.stdout.write(event.data.delta); } ``` `session.stream()` returns an `AsyncGenerator` — the final `return` value is the typed prompt result; the yielded values are the events leading up to it. ## Telemetry alongside events If `onEvent` is configured *and* an OTel exporter is configured (`@fabric-harness/sdk` `telemetry` option), both fire — they aren't mutually exclusive. Events are for in-process subscribers (logs, downstream workers, server-sent-event consumers); OTel is for cross-process observability backends. The runtime remains headless; `fh console` and `@fabric-harness/react` are optional clients of the public event protocol. ## Where to go next - [Sessions and prompts](/docs/building/sessions-prompts) — the call surface that emits these events. - [Telemetry](/docs/reference/telemetry) — exporting metrics and traces to Datadog, Grafana, OpenTelemetry collector. - [Approvals](/docs/building/approvals) — for the `approval_*` event variants. --- # Filesystem sources Canonical: https://harness.fabric.pro/docs/reference/filesystem-sources Mount read-only content (knowledge bases, docs, fixtures) into a sandbox so the agent can grep / glob / read it like ordinary files. `withFilesystemSources` pre-populates a sandbox with read-only content from one or more sources. The agent's built-in `grep`, `glob`, and `read` tools see it as ordinary files — no vector store, no embeddings, no retrieval pipeline. This is the right primitive for support agents, runbook lookup, FAQ assistants, internal-docs Q&A, or any workflow where the corpus is small enough to mount as files (low MB). ## Quickstart ```ts import { agent, localDirectorySource, withFilesystemSources } from '@fabric-harness/sdk'; const sandbox = withFilesystemSources('empty', [{ mountAt: '/workspace/kb', source: localDirectorySource('./knowledge-base', { include: (p) => p.endsWith('.md') }), }]); export default agent({ run: async ({ init, input }) => { const agent = await init({ sandbox, runtime: 'stateless' }); const session = await agent.session(); const message = String((payload as any)?.message ?? ''); return { reply: await session.prompt( `Search /workspace/kb (markdown) and answer concisely.\n\nCustomer: ${message}`, )}; }, }); ``` The agent sees `/workspace/kb/*.md` and uses its built-in tools to search. ## Sources ### `localDirectorySource(hostPath, options?)` Reads a host directory recursively. Each file becomes an entry; the relative path inside `hostPath` is preserved. ```ts localDirectorySource('./docs', { name: 'product-docs', include: (relativePath) => relativePath.endsWith('.md') && !relativePath.startsWith('drafts/'), }); ``` - `include` — predicate to skip files. Defaults to "include everything." - `name` — label used in telemetry. ### `inMemorySource(files, options?)` Bundle content directly into the agent module. Useful for fixtures, tests, or small static corpora that ship with the agent. ```ts inMemorySource({ 'faq.md': '# FAQ\n\nQ: Reset password? A: Visit /forgot-password.', 'guides/billing.md': '# Billing\n\nCharges happen on the 1st of each month.', }); ``` ### Cloudflare R2 sources `@fabric-harness/cloudflare` includes an R2 source helper for support knowledge bases and other object-backed file sets: ```ts import { r2FilesystemSource } from '@fabric-harness/cloudflare'; import { withFilesystemSources } from '@fabric-harness/sdk'; const sandbox = withFilesystemSources('empty', [{ mountAt: '/workspace/knowledge-base', source: r2FilesystemSource(env.SUPPORT_KB, { prefix: 'knowledge-base/' }), }]); ``` Objects such as `knowledge-base/reset-password.md` are mounted as `/workspace/knowledge-base/reset-password.md` and become available to the built-in `read`, `grep`, and `glob` tools. See `examples/support-agent-cloudflare-r2`. ### Concrete vendor connectors (recommended) `@fabric-harness/connectors` ships **concrete connectors** for the most common backends. Each is on its own subpath export and peer-deps the vendor SDK so you only install what you use. ```ts import { init } from '@fabric-harness/sdk'; import { s3Source } from '@fabric-harness/connectors/s3'; import { githubSource } from '@fabric-harness/connectors/github'; const fabric = await init({ sandbox: 'local' }); const session = await fabric.session(); // `session.mount()` writes the source's entries into the sandbox at the // given path. mode: 'read' (default) blocks write tools to that prefix. await session.mount('/mnt/logs', s3Source({ bucket: 'app-logs', prefix: '2026/' })); await session.mount('/mnt/repo', githubSource({ owner: 'me', repo: 'demo', ref: 'main' })); ``` Available subpath exports: | Export | Functions | Peer dep | |---|---|---| | `@fabric-harness/connectors/s3` | `s3Source`, `s3Writer` | `@aws-sdk/client-s3` | | `@fabric-harness/connectors/azure-blob` | `azureBlobSource`, `azureBlobWriter` | `@azure/storage-blob` | | `@fabric-harness/connectors/gcs` | `gcsSource`, `gcsWriter` | `@google-cloud/storage` | | `@fabric-harness/connectors/github` | `githubSource` | `octokit` | | `@fabric-harness/connectors/databricks-volume` | `databricksVolumeSource`, `databricksVolumeWriter` | _none_ | ### S3 and Azure Blob structural sources (advanced) For non-standard SDK clients or when you want to adapt an existing client, the older structural helpers remain available. They don't import cloud SDKs; adapt your client calls to the small interface shape. ```ts import { s3FilesystemSource, azureBlobFilesystemSource } from '@fabric-harness/connectors'; const s3 = s3FilesystemSource(s3ClientAdapter, { prefix: 'knowledge-base/' }); const azure = azureBlobFilesystemSource(blobClientAdapter, { prefix: 'knowledge-base/' }); ``` Use these with `withFilesystemSources()` the same way as local, in-memory, or R2 sources. ### Custom sources A source is just an object with an `entries()` method that yields `{ path, content }` pairs. Implement one for S3, R2, GCS, an MCP filesystem server, or any other backend: ```ts import type { FilesystemSource } from '@fabric-harness/sdk'; function s3Source(bucket: string, prefix: string): FilesystemSource { return { name: `s3://${bucket}/${prefix}`, async *entries() { // ...list & fetch from S3 yield { path: 'doc-1.md', content: '...' }; }, }; } ``` ## Composition `withFilesystemSources(base, sources)` accepts any sandbox base — a backend name, a `SandboxFactory`, or an existing `SandboxEnv`. It returns a `SandboxFactory` you pass to `init({ sandbox })`. Mount multiple sources at different paths: ```ts withFilesystemSources('empty', [ { mountAt: '/workspace/kb', source: localDirectorySource('./kb') }, { mountAt: '/workspace/runbooks', source: localDirectorySource('./runbooks') }, { mountAt: '/workspace/legal', source: localDirectorySource('./legal-templates') }, ]); ``` Wrap a non-empty backend so mounts coexist with the workspace: ```ts withFilesystemSources('local', [{ mountAt: '/workspace/reference', source: localDirectorySource('./reference'), }]); ``` ## Conventions and limits - **Read-only by default with `session.mount()`.** Calling `session.mount(mountAt, source)` (no options) marks the mount as read-only — write tools (`write`, `edit`, `mkdir`, `rm`) targeting paths under it are rejected at the policy layer. Pass `{ mode: 'write' }` to allow writes. - **Read-only by convention with `withFilesystemSources()`.** The legacy `withFilesystemSources()` factory does not enforce read-only — it relies on the agent's role/skill prompts. For hard enforcement, use `session.mount()` (default) or use a sandbox backend that supports it (Docker `--read-only` mounts via `metadata.mountReadOnly`, for example). - **Eager population.** Both `withFilesystemSources` and `session.mount()` walk the source(s) and write every file into the sandbox. This is the right model for small corpora; for very large knowledge bases use a retrieval-augmented approach instead. - **One source per path.** Mounting two sources at the same path is allowed but the second overwrites the first. Mount at distinct paths. - **Path rerooting.** `session.mount('/mnt/kb', source)` with sandbox cwd `/workspace` is rerooted to `/workspace/mnt/kb` since paths outside the sandbox boundary are blocked. ## See also - [Headless agents with the minimal entrypoint](/docs/getting-started/headless-mode) — the minimal entrypoint story this primitive belongs to. - [examples/support-agent](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/support-agent) — a runnable end-to-end example. - [Sandboxes](/docs/building/sandboxes) — the broader sandbox model. --- # HTTP Server Canonical: https://harness.fabric.pro/docs/reference/http-server REST, SSE, and WebSocket conventions for finite jobs and persistent agents on Node-derived targets. The Node server and Node-derived build targets share this v2 HTTP surface. Finite jobs support synchronous results or asynchronous run admission at `/jobs/:name`. Persistent agents are addressable instances at `/agents/:name/:id` and use durable, asynchronous admission by default. The Cloudflare target exposes finite jobs and core persistent prompt/read/delete routes with a smaller operational route set. See [Cloudflare Workers + Sandbox](/docs/deployment/cloudflare). Node applications can mount authenticated Fetch-standard routes and middleware without replacing this server. See [HTTP applications](/docs/building/http-applications). ## Routes | Method | Path | Purpose | |---|---|---| | `POST` | `/jobs/:name` | Invoke a finite job with its input payload. Returns `{ result, runId }`; add `?wait=false` for a `202` run receipt. | | `GET` | `/runs/:runId` | Read tenant-bound run status, output, error, timestamps, actor, and parent linkage. | | `GET` | `/runs/:runId/events` | Read run events with `?offset=&limit=` pagination. | | `POST` | `/runs/:runId/abort` | Propagate cancellation to an active run. | | `POST` | `/agents/:name/:id` | Durably admit `{ message, session? }` to a persistent instance. Returns `202 { submissionId, streamUrl, offset }`. Add `?wait=true` for a synchronous bridge. | | `POST` | `/agents/:name/:id/dispatch` | Enqueue `{ input, session? }` for asynchronous delivery. Returns a `202` dispatch receipt. | | `GET` | `/agents/:name/:id` | Read the persistent instance's default session state and history. | | `GET` | `/agents/:name/:id/conversation` | Read conversation records with `?session=&offset=&limit=` pagination. | | `GET` | `/agents/:name/:id/stream` | Tail conversation records with SSE when `?offset=` is present; without it, stream legacy session events. | | `GET` | `/agents/:name/:id/submissions/:submissionId` | Read queued, running, or settled submission status. | | `POST` | `/agents/:name/:id/abort` | Durably request abort for unsettled work in `{ session? }`. | | `GET` | `/agents/:name/:id/attachments/:digest` | Download a stored attachment when an attachment store is configured. | | `DELETE` | `/agents/:name/:id` | Cascade-delete the tenant-bound instance sessions, submissions, streams, and attachments supported by the configured stores. | | `GET` | `/health` | Liveness probe | | `GET` | `/ready` | Readiness probe with workspace info | | `GET` | `/builds` | List local build manifests | | `GET` | `/builds/:target/manifest` | Read a target's normalized build manifest. | | `GET` | `/sessions` | List sessions (auth-gated) | | `GET` | `/sessions/:id` | Inspect a session by id (regardless of agent name) | | `GET` | `/sessions/:id/events` | SSE stream by session id | | `POST` | `/sessions/:id/approvals/:approvalId/approve` (or `/reject`) | Resolve an approval. | ## Invoke a finite job ```sh curl -sS http://localhost:4317/jobs/summarize \ -H 'content-type: application/json' \ -d '{"text":"Durable agents need durable state."}' ``` Each call receives a new `runId`. Posting the same job to `/agents/summarize/:id` is not a compatibility alias; it returns `410 Gone` with the canonical `/jobs/summarize` route. For long-running jobs, admit the run and reconnect through the typed client: ```ts const receipt = await client.jobs.invoke( 'summarize', { text: 'Durable agents need durable state.' }, { wait: false, idempotencyKey: 'summary:document-42' }, ); const run = await client.runs.get(receipt.runId); const events = await client.runs.events(receipt.runId, { offset: '0' }); await client.runs.abort(receipt.runId); ``` `Idempotency-Key` scopes duplicate admission by tenant, authenticated principal, and job. Replaying the same key and payload returns the original run; reusing the key with a different payload returns `409 Conflict`. This prevents one principal from observing or taking over another principal's run. `X-Fabric-Parent-Run` links nested work initiated by an application or worker. The referenced run must exist and belong to the selected tenant; otherwise admission returns `400 Bad Request`. Run reads return `404` across tenant boundaries. ## Durable finite-run behavior Asynchronous finite jobs use the same durable submission store and lease coordinator as persistent agent work. This gives `/runs` consistent behavior across process restarts and multiple replicas: - an idempotent run interrupted by a process crash can be claimed and executed again; - a non-idempotent run interrupted after execution began settles as `failed` instead of silently repeating side effects; - an abort request is persisted, reaches an active `session.prompt()` through its abort signal, and reports the terminal outcome after settlement; - scheduled occurrences use deterministic idempotency keys and appear through the same run status and event routes; - `client.runs.observe()` drains the terminal event tail before returning. Use a shared submission store in multi-replica production deployments. In-memory storage provides the same API contract but cannot preserve work across a full process restart. ## Work with a persistent agent Prefer the typed client for admission, settlement polling, history, live observation, and aborts: ```ts import { createFabricClient } from '@fabric-harness/client'; const client = createFabricClient({ baseUrl: 'http://localhost:4317', headers: { authorization: `Bearer ${process.env.FABRIC_HARNESS_API_TOKEN}` }, }); const admitted = await client.agents.send({ agent: 'support', id: 'account-42', session: 'billing', message: 'Where is invoice 1007?', }); const settled = await client.agents.wait({ agent: 'support', id: 'account-42', submissionId: admitted.submissionId, }, { timeoutMs: 30_000 }); console.log(settled.outcome, settled.result); ``` `client.agents.prompt(...)` adds `?wait=true` and waits on the HTTP request. Use it for short compatibility flows, not as the default for durable or long-running work. ## Streaming events (SSE) For persistent agents, start at the offset returned by admission to catch up and then tail the conversation without a replay gap: ```sh curl -N 'https://my-app.example.com/agents/support/account-42/stream?session=billing&offset=0' ``` ```text data: {"offset":"0","records":[{"kind":"entry","entry":{"type":"user_prompt","text":"Where is invoice 1007?"}}]} data: {"offset":"1","records":[{"kind":"entry","entry":{"type":"assistant_message","text":"I found invoice 1007."}}]} ``` In a browser: ```ts const events = new EventSource(`/agents/support/${id}/stream?session=billing&offset=${offset}`); events.onmessage = (msg) => { const batch = JSON.parse(msg.data); console.log(batch.records); }; ``` For SDK session events rather than projected conversation records, omit `offset` or use `GET /sessions/:id/events`. Those events use the [`AgentEvent`](/docs/reference/events) taxonomy. ## WebSocket protocol `WS /sessions/:id/ws` opens a bidirectional WebSocket. Server pushes session events as JSON; the client can send commands back (cancel a task, approve a request, ping). Use this for interactive UIs that need to *send* messages, not just receive — for read-only streams, SSE is simpler and sufficient. The server side requires the optional `ws` peer dep (`pnpm add ws`); browsers and Node 22+ use the platform `WebSocket` natively for the client. ### Connecting ```ts import { connectFabricWs } from '@fabric-harness/sdk'; const handle = connectFabricWs({ url: `ws://localhost:4317/sessions/${id}/ws`, authToken: process.env.FH_AUTH_TOKEN, tenantId: 'tenant-acme', replay: 20, // last N events replayed on connect onEvent: (event) => console.log(event), onReplayEnd: () => console.log('live'), onAck: (ack) => console.log('ack', ack), onError: (err) => console.error(err), onClose: () => console.log('closed'), }); // Send commands back to the server: handle.send({ type: 'cancel', taskId: 'task-123', reason: 'user cancelled' }); handle.send({ type: 'approve', approvalId: 'appr-1', decision: 'approved' }); handle.send({ type: 'ping' }); ``` ### Server messages | `type` | Payload | |---|---| | `event` | `{ event: FabricEvent }` | | `replay-end` | sent once after the initial replay buffer is drained | | `pong` | `{ ts: number }` heartbeat | | `ack` | `{ for: 'cancel' \| 'approve', ok: boolean, message?: string }` | | `error` | `{ message: string }` | ### Client messages | `type` | Payload | |---|---| | `cancel` | `{ taskId: string, reason?: string }` | | `approve` | `{ approvalId: string, decision: 'approved' \| 'denied', reason?: string }` | | `ping` | `{ ts?: number }` | ### Auth + tenancy Browsers can't set headers on a WebSocket upgrade, so auth flows through query params: `?token=&tenant=`. The server validates with the same rules as HTTP (`authToken` / `authTokenEnv` options + `FABRIC_HARNESS_TENANT_REQUIRED=1`). Cross-tenant reads return a single `error` message and close. ### Heartbeats Server sends a `pong` every 15s. After 3 missed pings from the client (45s), the server closes the socket. Clients should pong on intervals or send periodic `ping` messages. ## Voice WebSocket `WS /sessions/:id/voice` opens a bidirectional voice bridge. Server creates an OpenAI Realtime connection on behalf of the client (provider API keys stay server-side). Same auth + tenant pipeline as the chat WS; same `ws` peer dep requirement. Query params customize per-connection: `?token=&tenant=&voice=alloy&model=gpt-4o-realtime-preview&audioFormat=pcm16&instructions=`. Server requires `OPENAI_API_KEY` in env; missing → `error` message and immediate close. Use `connectFabricVoice` (in `@fabric-harness/sdk`) for browser/Node clients — see [Voice](/docs/building/voice). ## Webhook trigger gating In **production mode** (`FABRIC_ENV=production` or `NODE_ENV=production`, and `FABRIC_MODE` is **not** `local` or `dev`), the server only exposes jobs and persistent agents that declare `triggers: { webhook: true }`. Other definitions return `403 Forbidden` to public invocation routes. Lift the gate locally with one of: ```sh FABRIC_MODE=local fh dev --target node FABRIC_MODE=dev fh dev --target node # Or simply not running in production mode ``` Behavior matrix: | `FABRIC_MODE` | `FABRIC_ENV` / `NODE_ENV` | Public route exposes | |---|---|---| | `local` or `dev` | any | every registered agent | | (unset) | `production` | only definitions with `triggers.webhook === true` | | (unset) | not production | every registered definition | ## Auth The dev server (`fh dev --target node`) accepts a bearer token in `Authorization: Bearer `. It is optional in local/dev mode. In production, requests other than `/health` and `/ready` fail closed with `401` unless a bearer token or custom `extractAuthToken` authorizer accepts them: ```ts import { startDevServer } from '@fabric-harness/node'; await startDevServer({ authToken: process.env.FABRIC_HARNESS_API_TOKEN, }); ``` Enterprise hosts can instead return a tenant-bound principal and scoped permissions. The same principal becomes the session actor, and the same authorization path protects HTTP and WebSocket requests: ```ts await startDevServer({ authenticate: async (req) => ({ id: await subjectFromVerifiedRequest(req), kind: 'user', tenantId: 'acme', permissions: ['agent:invoke', 'session:read', 'approval:read'], }), authorize: ({ principal, permission }) => permission !== 'admin:read' || principal.roles?.includes('operator') === true, }); ``` See [Authentication and RBAC](/docs/operating/auth) for the permission table, multi-tenant selection, and persistent-instance deletion behavior. Node-derived build targets read the same env var: `FABRIC_HARNESS_API_TOKEN`. In production, leaving it unset does not open the server; protected routes return `401`. ## Rate limiting Production mode enables fixed-window limits automatically. The defaults are 120 requests per minute, with separate budgets for prompts (30), streams and WebSockets (20), channel delivery (120), MCP (60), admin operations (60), and reads (300). Local development remains unlimited unless configured. Override the base or individual route classes with `rateLimit`: ```ts await startDevServer({ rateLimit: { windowMs: 60_000, maxRequests: 100, routes: { prompt: { maxRequests: 20 }, admin: { maxRequests: 10 }, }, }, }); ``` The built-in store is process-local. Multi-replica deployments can use the Redis adapter without adding a Redis SDK dependency to Fabric Harness: ```ts import { redisHttpRateLimiter, startDevServer } from '@fabric-harness/node'; import Redis from 'ioredis'; const redis = new Redis(process.env.REDIS_URL!); await startDevServer({ httpRateLimiter: redisHttpRateLimiter({ client: { eval: (script, keys, args) => redis.eval(script, keys.length, ...keys, ...args) as Promise, }, }), }); ``` Forwarded IP headers are ignored by default. List the direct ingress addresses when a trusted proxy terminates client connections: ```ts await startDevServer({ trustedProxies: ['10.0.4.12', '10.0.4.13'], }); ``` `trustedProxies: true` trusts any direct peer and is only appropriate when network policy prevents clients from reaching the server without passing through your ingress. ## Error responses | Status | Shape | Cause | |---|---|---| | `400` | `{ error: '...' }` | Bad input (e.g. invalid approval decision) | | `202` | `{ submissionId, streamUrl, offset }` | Persistent input was durably admitted and continues asynchronously. | | `401` | `{ error: { type: 'unauthorized', ... } }` | Missing or wrong bearer token | | `403` | `{ error: { type: 'forbidden', ... } }` | Agent has no webhook trigger in production mode | | `404` | `{ error: 'Not found' }` | Unknown session/agent | | `410` | `{ error: { type: 'gone', ... } }` | A finite job was posted to the removed `/agents/:name/:id` alias. | | `413` | `{ error: { type: 'body_too_large', ... } }` | Request body exceeded `maxBodyBytes` | | `429` | `{ error: { type: 'rate_limited', ... } }` | Rate limiter rejected the request | | `500` | `{ error: { type: 'internal_error', ... } }` | Uncaught exception | | `501` | `{ error: '...' }` | Endpoint exists but the configured store doesn't support it | ## See also - [Events](/docs/reference/events) — `AgentEvent` taxonomy used by the SSE stream - [Triggers](/docs/reference/triggers) — webhook / schedule / cli trigger declarations - [Persistent agents](/docs/building/persistent-agents) — authoring and delivery lifecycle - [Build and run artifacts](/docs/deployment/build-artifacts) — package this server for deployment - [Deployment → Cloudflare](/docs/deployment/cloudflare) — finite-job Worker surface and current limitations --- # License and attribution Canonical: https://harness.fabric.pro/docs/reference/license Apache License 2.0 terms, third-party attribution, and contribution expectations for Fabric Harness. Fabric Harness is distributed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). The repository `LICENSE` file is the authoritative license text. Every published package also carries `LICENSE` and `NOTICE` in its npm tarball and enables npm provenance. The license permits use, modification, and distribution subject to its conditions. Distributions must preserve the license and applicable copyright, attribution, and notice material. Modified files should carry appropriate change notices where the license requires them. Third-party packages retain their own licenses. The documentation renderer, for example, uses MIT licensed Mermaid libraries. Dependency license metadata should be included in release review and software-bill-of-materials output. The release gate packs and installs every public package in a clean project, imports each exported entrypoint, scans the tarballs for credential patterns, rejects incompatible or unresolved runtime licenses, and generates a CycloneDX SBOM. Run it before publishing: ```sh pnpm build pnpm check:release ``` Documentation and examples are maintained as independently authored Fabric Harness material. Do not submit proprietary code or documentation, or material whose license is incompatible with Apache-2.0. Contributors are responsible for preserving any attribution or `NOTICE` obligations that apply to material they introduce. --- # Live tests Canonical: https://harness.fabric.pro/docs/reference/live-tests Environment-gated integration tests for models, sandboxes, Azure, Databricks, Cloudflare, Docker, and Temporal. Fabric Harness keeps live provider tests opt-in. Default CI uses mocks and structural tests only; live tests require credentials and are skipped unless their `FABRIC_*_TEST=1` flag is set. ## Sandbox providers ### Daytona ```sh FABRIC_DAYTONA_TEST=1 \ DAYTONA_API_KEY=... \ pnpm --filter @fabric-harness/connectors test ``` Optional: ```sh DAYTONA_IMAGE=ubuntu:latest ``` The sandbox jobs create a provider resource, wrap it in `SandboxEnv`, and run `assertSandboxCertification()`. The report verifies shell, binary files, cwd/env, timeout, abort, streaming callbacks, portable refs, reconnect, and cleanup. Credentialed reports are uploaded as JSON CI artifacts without credentials or workspace content. ### E2B ```sh FABRIC_E2B_TEST=1 \ E2B_API_KEY=... \ pnpm --filter @fabric-harness/connectors test ``` Optional: ```sh FABRIC_E2B_PACKAGE=@e2b/code-interpreter E2B_TEMPLATE=... ``` ### Modal The Modal suite creates a native TypeScript SDK sandbox and validates the complete portable contract: ```sh FABRIC_MODAL_TEST=1 \ MODAL_TOKEN_ID=... \ MODAL_TOKEN_SECRET=... \ pnpm --filter @fabric-harness/connectors test ``` ## Azure ### Azure OpenAI ```sh FABRIC_AZURE_OPENAI_TEST=1 \ AZURE_OPENAI_ENDPOINT=... \ AZURE_OPENAI_API_KEY=... \ AZURE_OPENAI_DEPLOYMENT=... \ pnpm --filter @fabric-harness/azure test ``` ### Foundry Agent Service ```sh FABRIC_AZURE_FOUNDRY_TEST=1 \ AZURE_FOUNDRY_PROJECT_ENDPOINT=... \ AZURE_FOUNDRY_AGENT_ID=... \ AZURE_TOKEN=... \ pnpm --filter @fabric-harness/azure test ``` Optional: ```sh FABRIC_AZURE_FOUNDRY_POLL=0 ``` ### ARM tools ```sh FABRIC_AZURE_ARM_TEST=1 \ AZURE_SUBSCRIPTION_ID=... \ AZURE_TOKEN=... \ pnpm --filter @fabric-harness/azure test ``` Optional resources enable individual tests: ```sh AZURE_CONTAINER_APPS_JOB_RESOURCE_GROUP=... AZURE_CONTAINER_APPS_JOB_NAME=... AZURE_AKS_RESOURCE_GROUP=... AZURE_AKS_CLUSTER_NAME=... AZURE_AKS_TEST_COMMAND='kubectl get ns' AZURE_ACI_RESOURCE_GROUP=... AZURE_ACI_CONTAINER_GROUP=... AZURE_ACI_CONTAINER_NAME=... AZURE_ACI_TEST_COMMAND='echo fabric-harness-aci-smoke' ``` ## Databricks Use the package suite for low-level API diagnostics: ```sh FABRIC_DATABRICKS_TEST=1 \ DATABRICKS_HOST=https:// \ DATABRICKS_TOKEN=... \ pnpm --filter @fabric-harness/databricks test ``` Optional resources: ```sh DATABRICKS_WAREHOUSE_ID=... DATABRICKS_CATALOG=main DATABRICKS_SCHEMA=default DATABRICKS_JOB_ID=... FABRIC_DATABRICKS_WAIT_FOR_JOB=1 DATABRICKS_NOTEBOOK_PATH=/Repos/acme/smoke DATABRICKS_CLUSTER_ID=... # optional; omit for serverless notebook execution DATABRICKS_MLFLOW_RUN_ID=... DATABRICKS_WORKSPACE_ROOT=/Repos/acme ``` Use the certification gate for release evidence. It covers the App, ChatAgent proxy, AI Gateway, SQL and UC allow/deny, Vector Search and citation-validated RAG, MLflow managed RAG evaluation, Genie, Feature Serving, Lakeflow, Jobs, serverless notebook execution, Volumes, Lakebase, lineage, System Tables, and cost reconciliation: ```sh pnpm databricks:cert:plan FABRIC_DATABRICKS_PROVISION=1 pnpm databricks:cert:provision pnpm databricks:certify ``` Protected CI runs this gate daily, for published releases, and on demand. The OBO grant-difference check is intentionally manual: refresh the short-lived `DATABRICKS_OBO_TOKEN`, then dispatch the workflow with `require_obo` enabled. See [submission readiness](/docs/databricks/submission-readiness) for the complete variable matrix and evidence contract. ## Existing live test families Model providers: ```sh FABRIC_OPENAI_TEST=1 OPENAI_API_KEY=... pnpm --filter @fabric-harness/sdk test FABRIC_ANTHROPIC_TEST=1 ANTHROPIC_API_KEY=... pnpm --filter @fabric-harness/sdk test FABRIC_AZURE_OPENAI_TEST=1 AZURE_OPENAI_API_KEY=... AZURE_OPENAI_ENDPOINT=... pnpm --filter @fabric-harness/sdk test ``` Docker sandbox: ```sh FABRIC_DOCKER_TEST=1 pnpm --filter @fabric-harness/sdk test ``` Temporal: ```sh # Local/dev-server workflow smoke. Requires a reachable Temporal frontend. FABRIC_TEMPORAL_TEST=1 \ FABRIC_TEMPORAL_ADDRESS=localhost:7233 \ pnpm --filter @fabric-harness/temporal test -- integration.test.ts # Real-model Temporal smoke. Requires model/provider credentials too. FABRIC_TEMPORAL_REAL_MODEL_TEST=1 pnpm --filter @fabric-harness/temporal test -- live-model.test.ts ``` When using `temporalio/auto-setup` with Postgres in CI, set `DB=postgres12`, `DB_PORT=5432`, `POSTGRES_SEEDS`, `POSTGRES_USER`, and `POSTGRES_PWD`, then wait for a real Temporal gRPC connection before running tests. TCP port-open checks can pass before Temporal is ready. Cloudflare: ```sh FABRIC_CLOUDFLARE_TEST=1 \ FABRIC_CLOUDFLARE_WORKER_URL=... \ pnpm --filter @fabric-harness/cloudflare test ``` ## CI guidance - Keep default CI credential-free. - Run live tests in scheduled or manually triggered workflows. - Use isolated resource groups/workspaces/projects. - Prefer read-only tests unless a test is explicitly validating an execution target. - Clean up provider sandboxes with adapter `cleanup: true` where available. - Never echo tokens or provider SDK objects into prompts, logs, artifacts, or session history. --- # Policies and Approvals Canonical: https://harness.fabric.pro/docs/reference/policies-approvals Capability policies, approval flows, and where they live. Policies are enforced at tool dispatch and, by default, at the sandbox boundary. Definition-level policies act as a security floor: invocation code may narrow access but cannot replace allowlists or remove denials. ## Available controls - **Scoped commands.** Agents declare commands per call (`commands: [...]`). Anything else is a capability error. - **Capability-aware tools.** `write` and `edit` honor any filesystem write scope on the session. - **Approvals.** `session.approval.request({ reason, risk, timeout })` waits durably (Temporal target) or in-process (inline runtime). The capability policy block can be set on a finite job, `init()`, a session, or one prompt: ```ts import { init, policiedFetch, type CapabilityPolicy } from '@fabric-harness/sdk'; const policy: CapabilityPolicy = { filesystem: { read: ['/workspace/**'], write: ['/workspace/src/**', '/workspace/tests/**'], writeDeny: ['/etc/**', '/usr/**'], writeRequireApproval: ['**/.env*'], }, commandPolicy: { allow: ['npm test', 'git diff', 'git status'], requireApproval: ['git push*', 'rm -rf*'], }, network: { mode: 'allowlist', hosts: ['api.openai.com', '*.anthropic.com', 'api.github.com'], protocols: ['https:'], // defaults to ['http:', 'https:'] }, approvals: { defaultTimeoutMs: 5 * 60_000 }, }; const fabric = await init({ policy }); ``` ### Network policy The `network` block is enforced by tools that route HTTP through the SDK's `policiedFetch` helper. Wrap any `fetch`-like: ```ts const safeFetch = policiedFetch(fetch, policy); await safeFetch('https://evil.example.com'); // → throws FabricError { code: 'POLICY_DENIED', ... } ``` Modes: - `'allowlist'` — only hosts matching the `hosts` glob list are permitted (default when `hosts` is set). - `'denylist'` — listed hosts are blocked, everything else allowed. - `'none'` — block all outbound network. Useful for sandboxed analysis agents. ### Mount permissions `session.mount(mountAt, source, { mode: 'read' | 'write' })` (read by default) marks a mount as immutable. Write tools (`write`, `edit`, `mkdir`, `rm`) targeting paths under it are rejected by capability policy at the tool-call layer. ### Sandbox-layer enforcement (v0.10+) By default, `init({ policy })` auto-wraps the session's `SandboxEnv` with `policiedSandboxEnv()` so that raw `sandbox.exec` / `sandbox.writeFile` / `sandbox.readFile` calls from inside agent code honor the same `CapabilityPolicy` as tool dispatch. Without this, agent code that called `(await session.sandbox).exec('rm -rf /')` would bypass the policy that would have blocked the equivalent `session.shell()` call. Pass `bypassPolicyEnforcement: true` to `init()` if you have your own enforcement layered above (egress proxy, custom tools, etc.). ```ts import { init, policiedSandboxEnv } from '@fabric-harness/sdk'; const fabric = await init({ policy: { commandPolicy: { deny: ['rm -rf*'] }, filesystem: { writeDeny: ['/etc/**'] }, }, }); const session = await fabric.session(); const sandbox = await session.sandbox; // This now throws COMMAND_DENIED — the same policy that gates session.shell(). await sandbox.exec('rm -rf /tmp/data'); ``` The decorator throws `COMMAND_DENIED` for exec violations and `POLICY_DENIED` for filesystem violations. `requireApproval` patterns at the sandbox layer surface as denials with `details.approvalRequired: true` — the sandbox layer doesn't auto-resolve approvals because it has no session context. Use `session.shell()` / tool calls when you want approvals gated through the full machinery. ## Where to put policy - **Per prompt** — `session.prompt(text, { commands, policy })`. - **Per session** — `agent.session(id, { policy })`. - **Per finite job** — `job({ policy, run })`; this is a security floor for `run()` calls to `init()`. - **Per persistent agent** — return `{ policy }` from `defineAgent(({ id }) => ({ ... }))`. - **Shared application default** — pass `policy` to `init()` or merge a policy module from `.fabricharness/policies/` in your own config. See [Enterprise controls](/docs/building/enterprise-controls) for a complete definition-level example with tools, approvals, and cost budgets. ## Approvals from the CLI ```sh fh approvals --pending fh approve --actor preetham fh reject --actor preetham --reason "Wrong branch" ``` See [Approvals](/docs/building/approvals) and [`fh approvals`](/docs/cli/approvals). --- # Production Readiness Checklist Canonical: https://harness.fabric.pro/docs/reference/production-readiness What to validate before running Fabric Harness agents in production. Use this checklist to validate the exact runtime, providers, credentials, policies, and stores in your deployment environment. Generated Node artifacts use the shared v2 server; hosted targets add their own identity, networking, quota, and recovery requirements. ## Release baseline Before promoting a release: - Run `pnpm install --frozen-lockfile`. - Run `pnpm run check`. - Run `pnpm run build`. - Run `pnpm run test`. - Run the package suites with constrained concurrency if the workspace-wide parallel run times out; the current Databricks App persistence and CLI dev-banner tests are sensitive to full-workspace contention. - Pack publishable packages and inspect manifests for semver dependency ranges, not `workspace:*`. - Install `@fabric-harness/sdk@latest`, `@fabric-harness/node@latest`, and `@fabric-harness/cli@latest` in a clean ESM consumer project. - Run the downstream template mock suite against the release candidate version before publishing; existing `^2.0.1` template ranges resolve compatible `2.x` releases automatically. - Verify `fh --help` and SDK subpath imports. - Deploy the Fumadocs site and verify `https://harness.fabric.pro/docs` plus `llms.txt`. ## Choose a deployment path 1. Start with the `node` target and `virtual` sandbox for simple support/routing/extraction agents. 2. Use `local` only when CI/repo automation intentionally needs host tools. 3. Use Docker sandbox for untrusted shell or data-analysis work. 4. Use Temporal for long-running tasks, approval delays, and restart durability. 5. Use Cloudflare for edge and serverless workloads after running the account smoke test. 6. Run the documented environment validation for AKS, ACA, Databricks, E2B, Daytona, and Modal. 7. Use policies and approvals for every mutating command, tool, or external integration. 8. Put definition-wide enforcement controls on `AgentDefinition`; call-level policy may add restrictions but cannot loosen the definition policy or budget. ## Security gate - Define explicit filesystem, command, tool, and network policies. - Prefer deny-by-default command and network policy for unattended agents. - Route risky actions through `requireApproval` or custom approval rules. - Store secrets outside prompts and logs; verify redaction on representative failures. - Prefer Docker or a remote sandbox for untrusted code. Avoid host-local sandbox for untrusted workloads. - Configure tenant IDs when running multiple users/customers through the same deployment. - Return explicit principal permissions from `authenticate`; do not rely on the unrestricted legacy compatibility principal. - Grant operator-console users only the required `admin:read`, `session:abort`, and `session:delete` scopes. ## Runtime gate - Set request body limits, auth, and rate limits for every public HTTP surface. - Use durable session stores for production deployments; avoid in-memory stores outside tests. - Back up session/artifact stores according to your recovery objectives. - Verify cancellation, retry, and timeout behavior on your target runtime. - For Temporal, test worker restart, workflow retry, approval delay, and namespace/auth configuration. ## Observability gate - Export traces through OpenTelemetry, App Insights, Langfuse, or your chosen collector. - Track token usage, cost, duration, tool calls, approvals, and task lifecycle events. - Add dashboards/alerts for model failures, budget exhaustion, approval timeouts, rate limits, and sandbox failures. - Keep audit exports for compliance-sensitive agents. ## Hosted integration gate Run credentialed tests for every backend in the deployment: - Real model provider smoke tests. - Docker sandbox execution. - Postgres/Redis/session stores under expected concurrency. - Temporal local or cloud namespace tests, using a real gRPC readiness probe before integration tests. - Cloudflare Worker/R2 deployment smoke tests, including Shell Workspace `code` tool execution when using `mode: 'shell-workspace'`. - Azure OpenAI, Foundry, AKS, ACA, or ARM tests as applicable. - Databricks App deploy, Model Serving, SQL, Jobs, Volumes, Vector Search, Genie, Lakeflow, Feature Serving, OBO scope/denial, Lakebase, and System Tables through the certification evidence runner. - E2B, Daytona, Modal, Kubernetes, or other sandbox provider tests as applicable. ## API and release policy gate For every public release: - Publish and follow a SemVer/deprecation policy. - Document supported subpaths, deployment requirements, and behavioral constraints clearly. - Provide migration guides for minor versions with behavioral changes. - Generate public API reference from JSDoc. - Keep example projects runnable against the published npm packages. Record the command output, artifact digest, configuration version, and credentialed test evidence with the release. For Databricks, use the [submission readiness guide](/docs/databricks/submission-readiness) and certification evidence file. --- # Connector Recipes (`fh add`) Canonical: https://harness.fabric.pro/docs/reference/recipes Pipe Markdown connector recipes into Claude / Codex / Cursor / Aider / OpenCode and have them scaffold a Fabric Harness sandbox, MCP, knowledge-base, or data adapter into your project. `fabric-harness add` (alias `fh add`) supports direct scaffold recipes and TTY-aware Markdown connector guides. Use a scaffold when Fabric owns the file shape; use a guide when provider SDK versions, authentication, or project conventions need local adaptation. Recipes today are thin wiring on top of the package helpers — the package owns the tested implementation, the recipe wires it into your specific project layout, env vars, and lifecycle. For most providers you can also import the helper directly without scaffolding (see [Connector catalog](/docs/building/connector-catalog)). ## Common usage ```sh # List all available recipes (and the agent the CLI auto-detected for you) fh add fh add --json # Scaffold implementation + env example + test fh add slack fh add modal fh add channel slack fh add channel discord fh add database postgres fh add databricks lakebase fh add lakehouse fh add sandbox e2b --dir ./service fh add policy safe-defaults fh add channel slack --no-install fh add channel slack --dry-run # Review and apply a managed upgrade fh update slack --dry-run fh update channel slack --dry-run fh update channel slack # Pipe a known recipe to your coding agent fh add daytona | claude fh add github-mcp | codex fh add discord --install-deps | codex fh add mintlify-mcp | cursor-agent fh add fumadocs | aider # Build a brand-new connector from a provider's docs URL fh add https://e2b.dev --category sandbox | claude # Print the markdown without piping (for inspection) fh add daytona --print ``` For a unique managed alias, `fh add ` installs the recipe. Use `--print` to request the legacy Markdown guide explicitly. Names without a managed recipe retain the TTY-aware guide behavior. ## Categories Direct scaffold kinds: | Kind | Current recipes | | --- | --- | | `channel` | 17 maintained provider adapters plus generic webhook | | `database` | Postgres, MySQL, MongoDB, Redis, SQLite, libSQL, Turso, Supabase, Valkey | | `sandbox` | Daytona, E2B, Modal, Vercel, Cloudflare Sandbox, Cloudflare Shell, Boxd, exe.dev, Islo, Mirage | | `databricks` | core, sql, lakebase, vector-search, lakeflow, jobs, system-tables-cost, apps, analyst (see [Databricks recipes](/docs/databricks/recipes)) | | `tooling` | Braintrust, Jetty, OpenTelemetry, Sentry, Vitest Evals | | `model-provider` | OpenAI, Azure OpenAI | | `skill` | Analyze table | | `policy` | Safe defaults | Connector-guide categories: The Markdown recipes are grouped into four categories that map to Fabric Harness primitives: | Category | Maps to | Examples | |---|---|---| | `sandbox` | `SandboxFactory` | Daytona, E2B, Modal, Vercel Sandbox | | `mcp` | `connectMcpServer` | GitHub, Linear, Mintlify, Slack | | `kb` | `FilesystemSource` | Fumadocs (also `localDirectorySource`, `r2FilesystemSource`, `s3FilesystemSource`, `azureBlobFilesystemSource`, `httpFilesystemSource` in the SDK) | | `data` | `Command` / `ToolDef` | Postgres, Notion REST | | `channel` | `Channel` + outbound `ToolDef` | Discord, Messenger, Slack, Stripe, Teams, and the remaining channel catalog | | `database` | Scoped data tools and first-party unified persistence | libSQL/Turso, MongoDB, MySQL, Redis/Valkey, Supabase/Postgres | | `tooling` | Event/eval integration | Braintrust, Jetty, OpenTelemetry, Sentry, Vitest evals | ## Auto-detected coding agents `fh add` detects the agent you're running under and shows the matching pipe hint: | Detected | Pipe hint | |---|---| | Claude Code (`CLAUDE_CODE`, `CLAUDE_PROJECT_DIR`) | `\| claude` | | OpenAI Codex (`CODEX_AGENT`, `CODEX_HOME`) | `\| codex` | | Cursor Agent (`CURSOR_AGENT`, `CURSOR_TRACE_ID`) | `\| cursor-agent` | | Aider (`AIDER_RUNNING`) | `\| aider` | | OpenCode (`OPENCODE`) | `\| opencode` | | (none detected) | `\| claude` (canonical default) | Override by passing `--print` and piping yourself. ## Versions and safe updates Direct scaffolds declare aliases, a positive recipe version, compatible package ranges, managed files, upgrade notes, and package-manager-specific verification commands. Generated TypeScript and Markdown begin with `fabric-harness-recipe: /@`. `fh update` reads those markers and creates a complete plan before changing the project. It adopts unmarked legacy files only when they exactly match generated content, stops on user modifications, preserves compatible dependency ranges, and rejects ranges that cannot overlap the recipe contract. Use `--dry-run --json` in CI or automation. ## URL-form recipes When you pass a URL instead of a known recipe name, `fh add` emits a generic "scaffold a connector from these docs" prompt. The agent then fetches the URL and writes the adapter: ```sh fh add https://docs.modal.com --category sandbox | claude fh add https://api.intercom.io --category data | codex ``` The receiving agent gets: 1. The docs URL. 2. The category (so it knows whether to produce a `SandboxFactory`, `connectMcpServer` call, `FilesystemSource`, or `Command`/`ToolDef`). 3. A short template showing the file shape and the `secret()` pattern for credentials. ## Where the recipes live Direct scaffold recipes are typed definitions in `packages/cli/src/recipes/index.ts` and ship with the CLI. Connector guides are Markdown files under `connectors/` with local, bundled, and remote lookup: Recipes are Markdown files in `connectors/` at the repo root. The CLI looks for them in: 1. The user's current project (walking up to find `./connectors/.md`) 2. The CLI's own bundled copy (when developing this repo locally) 3. **Remote fallback** — `https://raw.githubusercontent.com/Fabric-Pro/fabric-harness/main/connectors/.md` So `fh add` works in any project that has `@fabric-harness/cli` installed, even without cloning fabric-harness. ## Authoring a new recipe 1. For a direct scaffold, add a typed recipe with a version, dependency ranges, managed files, and a verification command. 2. For a guide, create `connectors/--.md`. Use the existing guides as templates: short, agent-friendly, no secrets. 3. Add guide metadata to `CONNECTOR_RECIPES` in `packages/cli/bin/fabric-harness.ts`. 4. Add clean-install and update/conflict fixtures when changing generated files or dependencies. 5. Open a PR. Once merged, the direct recipe alias is available from the installed CLI; `--print` continues to resolve guide Markdown through the remote fallback. ## See also - [Filesystem sources](/docs/reference/filesystem-sources) — the `kb` category primitives - [MCP](/docs/reference/mcp) — the `mcp` category primitives - [Capability matrix](/docs/reference/capability-matrix) — what each connector type lets the agent do --- # Runtime modes Canonical: https://harness.fabric.pro/docs/reference/runtime-modes inline (default), stateless (headless), and temporal (durable). When to use each. `init({ runtime })` accepts three values. Runtime is independent from the SDK import entrypoint: agents from both `@fabric-harness/sdk` and `@fabric-harness/sdk` use the shared session primitives, while runtime controls persistence and durability. | Mode | Default? | Persistence | Use when | | --- | --- | --- | --- | | `inline` | yes | In-memory by default; pluggable `SessionStore` (SQLite/Postgres/file) for durability | You want conversation continuity, artifact persistence, approval gating, and CLI tooling like `fh sessions`/`fh inspect`. | | `stateless` | no — explicit opt-in | None. Each invocation is independent. | High-volume webhooks, edge runtimes, or prototypes where state would just be discarded. | | `temporal` | no — explicit opt-in | Durable workflow state via Temporal. | Long-running, restartable, multi-day agents that must survive crashes or human approval delays. Requires `@fabric-harness/temporal`. | ## inline (default) ```ts import { init } from '@fabric-harness/sdk'; const agent = await init({ model: 'openai/gpt-5.5', // runtime: 'inline' — implicit }); ``` In-memory `InMemorySessionStore` is used unless you pass `store`. History, artifacts, and approval state live for the lifetime of the process. Pass a real store when you need persistence: ```ts import { FileSessionStore } from '@fabric-harness/node'; const agent = await init({ model: 'openai/gpt-5.5', store: new FileSessionStore({ rootDir: '.fabricharness/sessions' }), }); ``` ### Production safety In production (`FABRIC_ENV=production` or `NODE_ENV=production`), `inline` without an explicit `SessionStore` emits a warning. The runtime still runs — you just lose state on restart. To silence the warning, either: - Configure a `SessionStore` (recommended). - Set `FABRIC_ALLOW_EPHEMERAL_STATE=1` to acknowledge in-memory state explicitly. - Use `runtime: 'stateless'` to opt into ephemeral semantics on purpose. ## stateless (headless) ```ts import { init } from '@fabric-harness/sdk'; const agent = await init({ model: 'openai/gpt-5.5', runtime: 'stateless', }); ``` The session uses `NoopSessionStore` — every write is discarded, every read returns the empty initial state. Use this for: - Webhook handlers where the response is the only output. - Cloudflare Workers / Vercel Edge where there's no durable storage attached. - Prototypes where you don't yet care about conversation continuity. ### What stateless disables - Session entry / event persistence. - Artifact persistence (`session.artifact(...)` falls back to a fingerprint-only `ArtifactRef` with no stored bytes). - Approval waiting via the store. Approvals only work in stateless mode if you pass an `onApproval` callback that returns a synchronous decision. - `fh sessions`, `fh inspect`, `fh logs` will not show stateless runs. If any of those matter, stay on `inline` and configure a store. ## temporal (durable) There are two ways to put an agent on the Temporal runtime — the **direct API** (recommended) and the **CLI-managed** flow. ### Direct API Wire `temporalSessionRuntime` into `init()` so each `prompt`/`task`/`shell`/`checkpoint.*` routes through Temporal workflows: ```ts import { init } from '@fabric-harness/sdk'; import { temporalSessionRuntime } from '@fabric-harness/temporal'; const fabric = await init({ runtime: 'temporal', sessionRuntime: temporalSessionRuntime({ address: 'localhost:7233', namespace: 'default', taskQueue: 'fabric-harness', }), }); const session = await fabric.session(); await session.prompt('Triage issue #42'); ``` The Temporal client connection is opened lazily on the first session and shared across all sessions produced by the factory. For tests, pass `mode: 'mock'` to use the in-process mock runtime. ### CLI-managed When running under `fabric-harness run --temporal`, the CLI constructs the Temporal session for you and you only declare the runtime mode: ```ts import { init } from '@fabric-harness/sdk/strict'; const fabric = await init({ runtime: 'temporal' }); ``` Sessions execute as Temporal workflows. The agent code is unchanged — the runtime adapter handles checkpointing, retries, and resume. See [Temporal worker deployment](/docs/deployment/temporal-worker) for setup. Temporal mode requires `@fabric-harness/temporal`, a Temporal cluster (or the bundled mock for tests), and a worker process running `fh temporal-worker`. ## Switching modes Modes are runtime-only. They are separate from the minimal vs complete import entrypoints and separate from deploy targets. Nothing in your agent code changes when you swap: ```ts const dev = await init({ runtime: 'stateless' }); // dev / preview const prod = await init({ runtime: 'inline', store: pgStore }); // prod web const longJob = await init({ runtime: 'temporal' }); // batch / nightly ``` Configure runtime at deploy time via `FABRIC_RUNTIME` env or your `.fabricharness/config.ts`. The agent file stays the same. ## See also - [SDK entrypoints, runtimes, and targets](/docs/reference/sdk-entrypoints-runtimes-targets) — the full conceptual split. - [Headless agents with the minimal entrypoint](/docs/getting-started/headless-mode) — the progressive-disclosure walkthrough. - [Session stores](/docs/reference/session-stores) — pluggable persistence options. - [Temporal worker deployment](/docs/deployment/temporal-worker) — setting up the worker. --- # Sandbox lifecycle Canonical: https://harness.fabric.pro/docs/reference/sandbox-lifecycle Auto-suspend, snapshot/fork, and sandbox refs — managing long-running compute durably. Long-running sandboxes (AKS pods, E2B sandboxes, Daytona workspaces) cost money while they are idle. Fabric Harness provides suspend, reconnect, snapshot, restore, and fork primitives so applications can reclaim compute without losing state. ## Auto-suspend on idle Pass `idleSuspendMs` to `init()` (or any sandbox factory option). After that many milliseconds with no operation, the sandbox is suspended. The next operation transparently resumes it. ```ts import { init } from '@fabric-harness/sdk'; const fabric = await init({ sandbox: 'docker', idleSuspendMs: 5 * 60_000, // suspend after 5 minutes idle autoResumeOnAccess: true, // default — auto-resume on next call }); ``` Backends that don't implement `suspend()` / `resume()` ignore the option (no-op pass-through). Currently supported: | Backend | Native support | |---|---| | `daytonaSandbox` | Yes, via `stop` / `start` | | `e2bSandbox` | Yes, via `pause` / `resume` | | `modalSandbox` | No native suspend | | `kubernetesSandbox` / `aksSandbox` | No automatic scale-to-zero | | `local` / `docker` / `empty` / `virtual` | Not applicable | ### How it works `createSandboxEnv()` wraps the env in a `SuspendingSandboxEnv` decorator when `idleSuspendMs > 0` and the inner env supports `suspend()`. The decorator: 1. Tracks `lastAccess` on every operation. 2. Schedules a `setTimeout` for `idleSuspendMs` from the last access. 3. Calls `inner.suspend()` when the timer fires. 4. On the next op, calls `inner.resume()` first (auto), then dispatches. 5. Clears the timer in `cleanup()`. The timer is `unref()`'d so it never keeps the Node process alive on its own. ### Manual control You can call `session.sandbox.suspend()` / `resume()` directly when capability is reported: ```ts const sandbox = await session.sandbox; if (sandbox.capabilities?.suspend) { await sandbox.suspend(); } ``` ### Strict mode Set `autoResumeOnAccess: false` to receive a `FabricError` (`SANDBOX_UNAVAILABLE`) instead of transparent resume. Useful when you want to explicitly gate operations on suspension state. ## Snapshot and fork `session.fork(label?)` captures the sandbox state and returns a `SandboxFork` handle. Calling `fork.attach()` spins up an independent session whose sandbox is a fork of the captured state — writes in branch A are invisible to branch B and to the origin. ```ts const session = await fabric.session('experiment'); await session.shell('npm install && npm test'); const fork = await session.fork('after-install'); // Run two parallel experiments from the same checkpoint: const branchA = await fork.attach(); const branchB = await fork.attach(); await Promise.all([ branchA.shell('node bench.js --variant fast'), branchB.shell('node bench.js --variant safe'), ]); ``` ### Capability ```ts const sandbox = await session.sandbox; if (sandbox.capabilities?.fork) { // session.fork() will work } ``` ### Backend support | Backend | Fork supported | |---|---| | `empty` / `virtual` | In-memory snapshot clone | | `local` | On-disk snapshot copied to a fresh workspace temp directory | | `docker` | On-disk snapshot copied to a fresh container workspace | | `e2bSandbox` / `modalSandbox` (provider-managed) | Uses `RemoteSandboxApi.fork()` when the provider supports start-from-snapshot | | `daytonaSandbox` | No; Daytona does not expose start-from-snapshot | | `databricksSqlSandbox` | No; SQL execution is stateless | | `cloudflareSandbox` | Use Durable Object state and R2 artifacts instead of sandbox forks | `session.fork()` throws `FabricError { code: 'SANDBOX_UNAVAILABLE' }` when the sandbox doesn't support fork. ### Cleanup Each forked session owns its own sandbox; calling `branch.sandbox.cleanup()` (or letting it fall out of scope) tears down only that branch. The origin is unaffected. For local/docker, fork creates a fresh temp workspace directory — the fork's `cleanup()` removes it (Docker) or leaves it for caller responsibility (Local). The shared `snapshotRoot` is **not** auto-removed; that's intentional so other forks of the same snapshot remain attachable. ## Sandbox refs Sometimes you want two sessions sharing the *same running sandbox* — not a fork, the actual instance — for multi-agent coordination, observability shadowing, or test isolation. `session.sandboxRef()` returns an opaque handle; pass it to `attachSandbox(ref)` in another session to participate in the same sandbox without taking ownership of its lifecycle. ```ts import { init, attachSandbox } from '@fabric-harness/sdk'; // Owner: creates the sandbox, owns cleanup. const ownerAgent = await init({ sandbox: 'docker', cwd: '/tmp/work' }); const owner = await ownerAgent.session('orchestrator'); await owner.shell('npm install'); const ref = await owner.sandboxRef(); // Attached: shares the same sandbox, does NOT own cleanup. const workerAgent = await init({ sandbox: attachSandbox(ref) }); const worker = await workerAgent.session('worker-1'); await worker.shell('npm test'); // runs in owner's sandbox // worker.cleanup() is a no-op for the underlying sandbox. // owner.cleanup() tears down for everyone. ``` ### Lifecycle rules - The first session to register a ref is the **owner**. Owner cleanup tears down the underlying sandbox. - Any session can `attachSandbox(ref)` to participate. Attached sessions are **non-owning**: their cleanup unregisters the attachment but doesn't touch the underlying sandbox. - After the owner cleanup, attached sessions see `FabricError { code: 'SANDBOX_UNAVAILABLE' }` on subsequent operations. The error message says "no longer alive (owner cleanup ran)" so debugging is unambiguous. - `session.sandboxRef({ portable: true })` returns a cross-process reference when the backend supplies `encodeRef()`. The receiving worker registers the provider decoder and calls `attachSandbox()`. ### When to use - **Multi-agent coordination**: an orchestrator and a worker both operating on the same workspace. - **Observability shadow**: a metrics/log-collecting session attached to a primary session's sandbox. - **Test scaffolding**: a setup phase that pre-populates a sandbox and hands the ref to the test session. When you want **independent branches** instead, use `session.fork()` (Phase B) above. Forks copy state at a point in time; refs share live state. ## Continuity negotiation Use `negotiateSandboxContinuity()` before selecting a recovery strategy. It reports reconnect, snapshot, restore, and fork separately, and returns `modes: ['none']` when the backend cannot preserve state. ```ts import { negotiateSandboxContinuity } from '@fabric-harness/sdk'; const continuity = negotiateSandboxContinuity(await session.sandbox); if (continuity.reconnect) { const ref = await session.sandboxRef({ portable: true }); await durableStore.put(`sandbox:${session.id}`, ref); } ``` ## Worker rotation Portable references include the owning tenant. A mismatched tenant is rejected before Fabric Harness calls the provider decoder. Durable workers should also use one ownership lease store shared by every replica: ```ts import { attachSandbox } from '@fabric-harness/sdk'; import { postgresSandboxOwnershipLeaseStore } from '@fabric-harness/node'; const factory = attachSandbox(ref, { tenantId: principal.tenantId, ownership: { store: postgresSandboxOwnershipLeaseStore(pg), ownerId: process.env.HOSTNAME!, leaseMs: 60_000, }, }); const sandbox = await factory({ sessionId }); ``` The lease is renewed at every sandbox operation. Once another worker claims an expired lease, the former worker cannot continue. Temporal activity adapters persist and recover portable references automatically when `sandboxOwnership` is configured. ## Reference - `SandboxEnv.suspend?(): Promise` - `SandboxEnv.resume?(): Promise` - `SandboxEnv.fork?(snapshot): Promise` - `SandboxCapabilities.suspend?: boolean` - `SandboxCapabilities.resume?: boolean` - `SandboxCapabilities.fork?: boolean` - `SandboxCapabilities.reconnect?: boolean` - `SandboxFactoryOptions.idleSuspendMs?: number` - `SandboxFactoryOptions.autoResumeOnAccess?: boolean` - `FabricSession.fork(label?): Promise` - `SandboxFork.attach(options?): Promise` - `FabricSession.sandboxRef(): Promise` - `attachSandbox(ref, options?): SandboxFactory` - pass to `init({ sandbox })`. - `negotiateSandboxContinuity(sandboxOrRef)` - inspect recovery modes. - `registerSandbox(env, options?)` / `unregisterSandbox(refId)` — direct registry access for advanced cases. - `SuspendingSandboxEnv` (class) — the suspend/resume decorator. - `withIdleSuspend(env, { idleSuspendMs })` — helper that wraps when applicable, returns env unchanged otherwise. --- # Sandboxes matrix Canonical: https://harness.fabric.pro/docs/reference/sandboxes-matrix Every supported sandbox backend at a glance — what it gives you, the example, and the one-liner to enable it. A sandbox is where shell commands and tool calls execute. It's chosen at `init({ sandbox })` and is **independent** of which SDK entrypoint you import from and which runtime you select. | Sandbox | What it gives you | Enable with | Example | |---|---|---|---| | `virtual` (default) | In-memory FS + bash subset (`grep`, `glob`, `read`, `cat`, `mkdir`, `rm`, `echo`) via [`just-bash`](https://github.com/vercel-labs/just-bash). No host shell access. | injected by default; `init({ sandbox: 'virtual' })` to be explicit | `examples/hello-world/` | | `local` | Host filesystem and host shell at the session's working directory. | `init({ sandbox: 'local' })` | `examples/with-local-shell/` | | `docker` | Per-session Docker container; reads/writes scoped to the container. | `init({ sandbox: { backend: 'docker', image: 'node:22' } })` | `examples/with-docker/` | | `cloudflare` | Cloudflare Sandbox container binding for Workers. Edge-native. | `init({ sandbox: await getCloudflareSandbox(env.SANDBOX, sessionId) })` (from `@fabric-harness/cloudflare`) | `examples/with-cloudflare-sandbox/` | | Temporal-driven `local` | Runs through Temporal workflows for replay-determinism, restartability, approvals. | `import { agent } from '@fabric-harness/sdk/strict'` + `init({ runtime: 'temporal', sandbox: 'local', compaction: { enabled: false } })` | `examples/with-temporal/` | | Azure Foundry Hosted | Azure-managed agent runtime; Key Vault secrets, Foundry observability. | `AzureOpenAIModelProvider` + `fabric-harness build --target foundry-hosted-agent` | `examples/with-azure/` | | Daytona | Daytona-managed remote dev sandbox. Per-task ephemeral box. | `init({ sandbox: daytonaSandbox(remote, { cleanup: true }) })` (from `@fabric-harness/connectors`) | `examples/with-daytona/` | | E2B | E2B remote sandbox; native pause/resume. | `init({ sandbox: e2bSandbox(remote, { cleanup: true }) })` (from `@fabric-harness/connectors`) | recipe `connectors/sandbox--e2b.md` | | Modal | Modal serverless sandbox; per-request GPU. | `init({ sandbox: modalSdkSandbox(remote, { cleanup: true }) })` (from `@fabric-harness/connectors/modal`) | `examples/with-modal/` | | Vercel Sandbox | Vercel's ephemeral container compute with streamed output, abort, and portable refs. | `init({ sandbox: vercelSandbox(remote, { cleanup: true }) })` (from `@fabric-harness/connectors/vercel`) | `examples/with-vercel-sandbox/` | | Kubernetes / AKS | Kubernetes pod via `kubernetesSandbox(pod, …)` (or `aksSandbox` from `@fabric-harness/azure`). | `init({ sandbox: kubernetesSandbox(pod, { cleanup: true }) })` (from `@fabric-harness/connectors/k8s`) | `examples/with-kubernetes/` | | Databricks SQL | SQL Warehouse exec-only sandbox; `session.shell()` executes SQL, not bash. | `init({ sandbox: databricksSqlSandbox({ host, token, warehouseId }) })` (from `@fabric-harness/databricks/sql-sandbox`) | [Databricks SQL sandbox](/docs/ecosystem/sandboxes/databricks-sql) | | `empty` | No filesystem, no shell — pure model + tool-call work. | `init({ sandbox: 'empty' })` | n/a | Provider-backed sandboxes require provider credentials, lifecycle permissions, and the smoke test documented on their setup page. For Databricks SQL, validate Warehouse connectivity and Unity Catalog grants in the target workspace. > **Cross-process refs.** Remote backends (E2B, Daytona, Modal, Vercel, Kubernetes, Cloudflare Sandbox, and Cloudflare Shell with a routing ID) can be re-attached from another process with `session.sandboxRef({ portable: true })` + `attachSandbox(serialized)` after registering the receiving-process decoder. See [`@fabric-harness/connectors/sandbox-refs`](https://github.com/Fabric-Pro/fabric-harness/blob/main/packages/connectors/src/sandbox-refs.ts). ## Picking a sandbox - **One-shot webhook / edge worker** — `virtual` (cheap, fast, no host) or `cloudflare` (Workers). - **CI job, dev box** — `local`. - **Coding agent / untrusted shell work** — `docker`, `daytona`, or `modal`. - **Long-running, restartable** — Temporal-driven `local` (use `/strict`). - **Compliance / Azure tenant** — Azure Foundry Hosted Agent. - **Governed analytics where shell means SQL** — Databricks SQL sandbox. ## Capability axes Every sandbox declares a `SandboxCapabilities` shape so agents can adapt: ```ts sandbox.capabilities // { exec, network, filesystem, snapshot, ... } ``` Use this when an agent legitimately needs to know whether shell exec is available before issuing a command. ## See also - [Sandboxes overview](/docs/building/sandboxes) — the conceptual model. - [Sandbox connectors](/docs/building/sandbox-connectors) — building your own `RemoteSandboxApi`. - [Capability matrix](/docs/reference/capability-matrix) — validation level and operational guidance by backend. --- # SDK entrypoints, runtimes, and targets Canonical: https://harness.fabric.pro/docs/reference/sdk-entrypoints-runtimes-targets One default import, an explicit `/strict` opt-out, and orthogonal runtime + target choices. Fabric Harness has **one SDK** with three independent choices: 1. **Entrypoint** — `@fabric-harness/sdk` (default) or `@fabric-harness/sdk/strict` (no defaults). 2. **Runtime** — `stateless`, `inline`, or `temporal`. Controls persistence and durability. 3. **Target** — `node`, `temporal-worker`, `docker`, `cloudflare`, `foundry-hosted-agent`. Where it runs. Keeping these separate lets a 10-line webhook grow into a durable, audited, Temporal-backed background agent without changing the core `init()` / `agent.session()` / `session.prompt()` / `session.skill()` / `session.task()` / `session.shell()` primitives. ## 1. SDK import entrypoints | Entrypoint | Import | Use it when | What's different | |---|---|---|---| | **Default** | `@fabric-harness/sdk` | Almost always. New projects, prototypes, headless edge agents, production webhooks, support agents, even most coding agents. | `agent({...})` auto-injects headless defaults at every `init()`: `runtime: 'stateless'`, `sandbox: 'virtual'`, `loopRuntime: pi-agent-core`, `compaction: { enabled: true }`. Override any value to opt out per call. | | **Strict** | `@fabric-harness/sdk/strict` | Temporal-backed durable agents (replay determinism), compliance / audit / regulated workloads, production agents that prefer no implicit behaviour. | Identical call shape and exports — but `agent({...})` does **not** inject defaults. Every option must be declared explicitly. | > ⚠️ Selecting `runtime: 'temporal'` from the **default** import emits a one-time runtime warning — auto-compaction is non-deterministic across Temporal replay. Use **`/strict`** for Temporal, or pass `compaction: { enabled: false }` explicitly. ```ts title="Default — most agents" import { agent } from '@fabric-harness/sdk'; export default agent({ name: 'echo', run: async ({ init, input }) => { const session = await (await init()).session(); return { reply: await session.prompt(String(input?.message ?? '')) }; }, }); ``` ```ts title="Default — same import, with typed schemas + policy + telemetry" import { agent, schema } from '@fabric-harness/sdk'; import type { CapabilityPolicy } from '@fabric-harness/sdk'; const policy: CapabilityPolicy = { commandPolicy: { allow: ['gh issue view*'], requireApproval: ['gh issue comment*'] }, }; export default agent({ name: 'triage', input: schema.object({ issueNumber: schema.number(), title: schema.string() }), output: schema.object({ severity: schema.enum(['low','medium','high']), summary: schema.string() }), run: async ({ init, input }) => { const fabric = await init({ policy }); const session = await fabric.session(); return await session.prompt(`Triage issue #${input.issueNumber}: ${input.title}`); }, }); ``` ```ts title="Strict — Temporal / compliance" import { agent, schema } from '@fabric-harness/sdk/strict'; export default agent({ name: 'migrate', input: schema.object({ description: schema.string() }), output: schema.object({ filename: schema.string(), upSql: schema.string() }), run: async ({ init, input }) => { // Strict: every option is declared. Nothing implicit. const fabric = await init({ runtime: 'temporal', sandbox: 'local', compaction: { enabled: false }, // explicit; replay-deterministic policy: { commandPolicy: { requireApproval: ['psql*'] } }, }); const session = await fabric.session(); // ... }, }); ``` ## 2. Runtime modes | Runtime | State model | Best for | Tradeoff | |---|---|---|---| | `stateless` | No persisted session history. Each invocation independent. | High-volume webhooks, edge/serverless, simple headless automations, prototypes. | No durable artifacts, no stored approvals, no resumable history. | | `inline` | Runs in the current process. In-memory state by default; configure a `SessionStore` for persistence. | Local dev, Node servers, CI jobs, production web services with SQLite/Postgres/file storage. | Process-local unless you configure a durable store. | | `temporal` | Model calls, shell commands, checkpoints, approvals, and child tasks run through Temporal workflows/activities. | Long-running background agents, restartable jobs, approval-gated workflows, multi-day runs. | Requires a Temporal server and a `fh temporal-worker` process. | Runtime is **not** tied to entrypoint. From the default import, you get `runtime: 'stateless'` unless you override; from `/strict` you must declare it. Both reach the same execution paths. ## 3. Deployment targets | Target | CLI | What it means | |---|---|---| | `node` | `fh run agent --target node` / `fh build --target node` | Run or build a Node HTTP/server process. | | `temporal-worker` | `fh temporal-worker` and `fh run agent --target temporal-worker` | Start a worker; drive agents through Temporal workflows. | | `docker` | `fh build --target docker` | Package a Node build in a Docker-ready output. | | `cloudflare` | `fh build --target cloudflare` | Cloudflare Worker bundle. | | `foundry-hosted-agent` | `fh build --target foundry-hosted-agent` | Hosted-agent deployment artifact for Azure Foundry. | Target is **where** the code runs. Runtime is **how** sessions execute. Entrypoint is **what** the agent imports. ## Common combinations | Scenario | Entrypoint | Runtime | Target | |---|---|---|---| | 10-line webhook | default | `stateless` | `node` or `cloudflare` | | Support agent with mounted KB | default | `stateless` or `inline` | `node` or `cloudflare` | | CI issue triage | default | `inline` | `node` | | Production Node service | default | `inline` + SQLite/Postgres/file store | `node` or `docker` | | **Durable issue triage** | **`/strict`** | `temporal` | `temporal-worker` | | **Approval-gated background job** | **`/strict`** | `temporal` | `temporal-worker` | | **SOC-2 / HIPAA / FedRAMP-style audit** | **`/strict`** | `inline` | any | ## Moving between import paths Swap one import line. Nothing else changes: ```diff - import { agent } from '@fabric-harness/sdk'; + import { agent } from '@fabric-harness/sdk/strict'; ``` The agent body stays the same. The only effect: defaults are no longer auto-injected, so any field you previously relied on a default for must be declared explicitly. ## Why both exist | Question | Answer | |---|---| | Why have a `/strict` import at all? | **Replay determinism + audit clarity.** Auto-compaction is non-deterministic for Temporal replay. Hidden defaults make compliance reviews harder. Strict makes every choice visible in the agent file. | | Why isn't strict the default? | Most agents don't need it. Asking every user to declare four options on every agent is friction. | | Can I use Cloudflare R2, Docker, Daytona, OTel telemetry, custom stores from the default import? | **Yes.** All of those are values you pass to `init()` or to `agent({...})`. They're independent of which entrypoint you import from. | | Can I move from default to strict later? | Trivially. Change the import, declare any options that were previously defaulting. | | What about heavy provider/Temporal-client deps? | Already in separate packages: `@fabric-harness/temporal`, `@fabric-harness/cloudflare`, `@fabric-harness/azure`, `@fabric-harness/connectors`. The bare SDK is small. | ## See also - [Agent anatomy](/docs/building/anatomy) — the `agent({...})` shape. - [Runtime modes](/docs/reference/runtime-modes) — `stateless` vs `inline` vs `temporal` in detail. - [HTTP server](/docs/reference/http-server) — `/agents/:name/:id` REST + SSE. - [Compaction](/docs/reference/compaction) — when and how auto-compaction triggers. - [Capability matrix](/docs/reference/capability-matrix) — what each piece can do. --- # Security Hardening Canonical: https://harness.fabric.pro/docs/reference/security-hardening Checklist before promoting an agent to production. The full hardening notes live in the repo at [`docs/security-hardening.md`](https://github.com/). This page is the short-form checklist. ## Secrets - Never paste keys into source files or session artifacts. - Use `secret()` and `resolveSecret()` to keep credentials out of model context. - Drive `.env` only through `--env`; shell env always wins. - Scope command env via `defineCommand('cmd', { env: { ... } })`. ## Capability scoping - Only declare commands the agent actually needs. - Prefer narrow filesystem scopes: `/workspace/src/**`, not `/workspace/**`. - Gate destructive actions (`git push`, `npm publish`, `terraform apply`) behind `session.approval.request()`. ## Network - Default to allowlist egress. - For Cloudflare deployments, prefer routes scoped to the agent path. - For Foundry Hosted Agents, configure BYO VNet egress where possible. ## Build-time integrity - Emit SBOMs on every build (`--sbom`, `--image-sbom`). - Emit provenance and attestation (`--provenance`, `--attestation`). - Sign with cosign (`--sign-provenance`). - Verify in deploy with `fh verify-attestation` / `fh verify-provenance`. ## Runtime - Require auth on `fh dev` / Node target via `--auth-token-env`. - Set `--max-body-bytes` to bound payload sizes. - Set `--rate-limit-window-ms` and `--rate-limit-max` per IP. - Run sandboxes with the least permissive backend that still works (`empty` > `local` > `docker` > host). ## Audit - Keep session stores; don't disable them in production. - Use compaction sparingly — keep the active path long enough for audit needs. - Log Entra Agent ID + on-behalf-of identities on Foundry deployments. --- # Session Stores Canonical: https://harness.fabric.pro/docs/reference/session-stores Unified memory, SQLite, Postgres, Redis, MySQL, MongoDB, and libSQL/Turso persistence. For new Node applications, configure a complete persistence bundle rather than a session store in isolation. A bundle keeps session history, durable submissions, conversation offsets, attachments, run events, cost totals, health, and deletion on one backend: ```ts title=".fabricharness/config.ts" import { sqlitePersistence } from '@fabric-harness/node'; export default { persistence: sqlitePersistence({ path: '.fabricharness/fabric-harness.sqlite', }), }; ``` Use `memoryPersistence()` for ephemeral tests, `sqlitePersistence()` for a durable single process, and `postgresPersistence()` or `redisPersistence()` for high-throughput multiple replicas. `mysqlPersistence()`, `mongodbPersistence()`, and `libsqlPersistence()` provide the same complete contract through version-fenced snapshots for moderate operational state. Third-party backends can verify the same behavior with `definePersistenceBundleContractTests` from `@fabric-harness/sdk/testing/contracts`. The session store is where Fabric persists session entries, events, tasks, approvals, checkpoints, and artifact metadata. The default is a file store. The full notes live at [`docs/session-stores.md`](https://github.com/). ## Backends | Backend | Where it stores | When to use | | --- | --- | --- | | **File** (default) | `.fabricharness/sessions//` | Local development, CI smoke tests, single-host services. | | **SQLite** | `.fabricharness/sessions.sqlite` (configurable) | Single-host services that want SQL queries and atomic writes. | | **Postgres** | external Postgres | Multi-host services, Temporal worker fleets, production. | | **Redis / Valkey** | external Redis-compatible cluster | Multi-host services needing low-latency leases, offsets, retention TTLs, and atomic budgets. | | **MySQL** | versioned snapshot rows in MySQL 8 | Existing MySQL estates with moderate agent-state write volume. | | **MongoDB** | versioned snapshot documents | Existing Mongo deployments; standalone or replica set. | | **libSQL / Turso** | local file or remote versioned snapshot rows | Lightweight local/serverless deployments with moderate state. | ## Configuration `.fabricharness/config.ts`: ```ts export default { store: { backend: 'sqlite', path: '.fabricharness/sessions.sqlite', }, }; ``` ```ts export default { store: { backend: 'postgres', connectionString: process.env.DATABASE_URL, }, }; ``` ## What gets stored For each session: - session header (id, created, updated, agent), - ordered entries (prompts, assistant turns, tool calls, shell commands, task events, approvals, compactions, checkpoints), - the event stream, - artifact metadata + content (or external blob URLs), - task records, - approval records. ## Examples - [`examples/with-config`](https://github.com/) — central config including SQLite session storage. - [`examples/with-postgres-store`](https://github.com/) — Postgres-backed session/artifact storage. - [`examples/database-persistence`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/database-persistence) — select libSQL/Turso, MySQL, or MongoDB from one config. --- # Telemetry Canonical: https://harness.fabric.pro/docs/reference/telemetry Events, metrics, and OpenTelemetry hooks. Fabric Harness emits structured events for every meaningful step in an agent run. The full notes live at [`docs/telemetry.md`](https://github.com/). ## Event types ``` agent_start text_delta prompt_start prompt_end tool_start tool_end command_start command_end task_start task_end approval_requested approval_granted approval_denied approval_expired checkpoint_created checkpoint_restored compaction mount metric result_retry result error ``` ## Transports - **SSE** — `GET /agents/:name/:id/stream` emits typed `AgentEvent` values for any consumer (curl, fetch, downstream worker). - **OpenTelemetry** — `openTelemetryExporter` adapts Fabric's `TelemetrySpan` shape to any `@opentelemetry/api` Tracer (App Insights, Jaeger, Honeycomb, generic OTLP). Pass `conventions: 'foundry'` to emit `gen_ai.*` span names matching Azure AI Foundry / OpenTelemetry Generative AI semantic conventions. - **Langfuse** — `langfuseExporter` for direct trace export (peer-deps `langfuse`). - **Azure Monitor / App Insights** — `applicationInsightsExporter` from `@fabric-harness/azure/app-insights` (peer-deps `applicationinsights`). - **Console** — `consoleTelemetryExporter` for local debugging. ```ts import { openTelemetryExporter, langfuseExporter, eventToTelemetrySpan } from '@fabric-harness/sdk'; import { Langfuse } from 'langfuse'; const langfuse = new Langfuse({ publicKey, secretKey, baseUrl }); const exporter = langfuseExporter({ client: langfuse, attributes: { service: 'support-agent' } }); const session = await fabric.session(undefined, { onEvent: async (event) => { const span = eventToTelemetrySpan(event); if (span) await exporter.export(span); }, }); ``` ## Metrics from the CLI ```sh fh metrics [--json] ``` Aggregates: - token usage (input + output + total) and `costUsd` if the provider reports it, - tool calls (with per-tool breakdown for the top 5), - shell commands and durations, - artifacts, - mounts (count, total bytes, total files, top sources by bytes), - model attempts. ## Per-event hooks Subscribe to events at agent or session scope via the `onEvent` callback. Wire any of the exporters above (or a custom one implementing `TelemetryExporter`) into the callback to forward spans. ## Hierarchical OpenTelemetry observer `openTelemetryExporter` emits one flat span per duration-bearing event. For a **nested trace** — a single parent span per operation, with `turn`, `tool`, and `shell` spans inside — use the observer instead: ```ts import { trace } from '@opentelemetry/api'; import { createOpenTelemetryObserver } from '@fabric-harness/sdk/otel-observer'; const observe = createOpenTelemetryObserver({ tracer: trace.getTracer('fabric-harness'), conventions: 'fabric', // or 'foundry' for gen_ai.* span names + attributes }); const agent = await init({ onEvent: observe }); ``` It lives on the `@fabric-harness/sdk/otel-observer` subpath (not the main entry) because building parent spans needs `@opentelemetry/api` at runtime, while the main SDK keeps it an optional peer dependency. The tree is inferred from event start/end ordering; a `task()` sub-session traces separately with a wrapping `task` span, and an `error` event closes open spans with `ERROR` status. --- # Triggers and Public Route Gating Canonical: https://harness.fabric.pro/docs/reference/triggers How finite jobs and persistent agents are exposed once deployed. Triggers are definition metadata that tell generated servers which entrypoints may be exposed in production. Put triggers inside `job({...})` or return them from `defineAgent(...)`. ## Declaring triggers ```ts import { job } from '@fabric-harness/sdk'; export default job({ name: 'webhook-triage', triggers: { webhook: true, }, async run({ input }) { return input; }, }); ``` ## Default behavior - **Finite job:** `webhook: true` enables `POST /jobs/:name` in production. - **Persistent agent:** `webhook: true` enables `POST /agents/:name/:id` in production. - Without it, the definition remains available to local/dev and CLI flows, but production public invocation returns `403 Forbidden`. ## Public route gating For Node-derived deployments, gate public routes with: - `--auth-token-env ` (CLI / env) requiring a bearer token. - `--max-body-bytes` to cap body size. - `--rate-limit-window-ms` + `--rate-limit-max` for per-IP rate limits. For Cloudflare, apply route-level rules around `/jobs/` and `/agents//` in addition to the Worker bearer token. ## Schedule triggers The Node server runs finite `schedule` triggers with `cron-parser`: ```ts export default job({ name: 'daily-report', triggers: { schedule: '0 9 * * 1-5' }, async run({ input }) { /* ... */ }, }); ``` Configure timezone and payload resolution when embedding the server: ```ts await startDevServer({ scheduler: { timezone: 'America/Phoenix', payload: (jobName, expression, scheduledAt) => ({ scheduledAt: scheduledAt.toISOString() }), }, }); ``` Set `FABRIC_HARNESS_SCHEDULER_ENABLED=0` to disable it. The scheduler calculates each next cron boundary before execution, skips overlapping runs of the same job, emits normal session events, and stops its timers during graceful shutdown. ### Multiple Node replicas Use the Postgres lease store so every cron occurrence and active job has one owner across replicas: ```ts import { Pool } from 'pg'; import { postgresSchedulerLeaseStore, startDevServer } from '@fabric-harness/node'; const pool = new Pool({ connectionString: process.env.DATABASE_URL }); await startDevServer({ scheduler: { leaseStore: postgresSchedulerLeaseStore(pool), leaseMs: 60_000, }, }); ``` The active lease renews while the job runs. An occurrence claim remains long enough to prevent a late or restarted replica from replaying the same cron tick. `memorySchedulerLeaseStore()` is the single-process default and is useful for tests, not multi-replica coordination. Each claimed Node occurrence is admitted as a durable finite run. It receives a run receipt, can be inspected through `/runs/:runId`, and deduplicates across replicas using the schedule expression and scheduled timestamp. If an occurrence is more than Node's maximum timer delay away, the scheduler re-arms the same occurrence at each timer boundary; it does not execute the job early. ### Cloudflare Cron Triggers `fh build --target cloudflare` discovers every schedule expression, writes it to `wrangler.jsonc` under `triggers.crons`, and emits a Worker `scheduled()` handler. Each occurrence gets a deterministic run ID and executes through the same finite-job function as `POST /jobs/:name`. Inspect it with `GET /runs/:runId` and read offset events from `GET /runs/:runId/events?offset=0`. Cloudflare serializes duplicate deliveries for that run ID through its Durable Object and returns the existing run instead of executing twice. Cron expressions use the target runtime's timezone rules: Node accepts an explicit IANA timezone; Cloudflare Cron Triggers run in UTC. During DST changes, `cron-parser` advances missing wall-clock times to the next valid instant and executes a repeated wall-clock occurrence once. --- # Use Cases Canonical: https://harness.fabric.pro/docs/use-cases Ten Fabric Harness agents you can build today, across the software development lifecycle — issue triage, PR review, changelog drafting, test generation, dependency audit, migrations, API docs, bug repro, release notes, and incident runbooks. Every example shows both minimal and complete entrypoint forms. import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Each use case below shows two equivalent forms: - **Minimal** — `agent({...})` from `@fabric-harness/sdk`. Headless defaults pre-injected (stateless runtime, virtual sandbox, pi-agent-core, auto-compaction). - **Complete** — same `agent({...})` from the same `@fabric-harness/sdk` import — but with typed `input`/`output` schemas, capability `policy`, artifacts, custom stores, providers, telemetry. *No import path change required.* > For **Temporal-backed durable agents**, **compliance/audit workloads**, or any case where you want zero implicit behavior, import from `@fabric-harness/sdk/strict` instead — same call shape, no headless defaults injected. ## How to test these locally Each agent below has a runnable scaffold in [`examples/`](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples) with a default fixture. Clone, install, run: ```sh git clone https://github.com/Fabric-Pro/fabric-harness cd fabric-harness && pnpm install && pnpm build export OPENAI_API_KEY=... pnpm --filter @fabric-harness/example-changelog-writer run ``` Or in your own project: ```sh npm install @fabric-harness/sdk @fabric-harness/cli fabric-harness run --payload '...' ``` ## Connecting to external knowledge bases | Source | Pattern | Notes | |---|---|---| | Local Markdown / MDX | `localDirectorySource('./kb')` | Eager mount; agent uses `grep`/`read` | | Fumadocs content tree | `fumadocsSource('./apps/docs/content/docs')` | Strips frontmatter | | Mintlify (local) | `mintlifySource('./mint-docs')` | Includes `docs.json` | | Mintlify (hosted MCP) | `connectMcpServer('mintlify', { url: '...mcp', transport: 'streamable-http' })` | Default transport is streamable-http | | Cloudflare R2 | `r2FilesystemSource(env.KB)` | From `@fabric-harness/sdk/cloudflare` | | S3 / Azure Blob | `s3FilesystemSource(...)` / `azureBlobFilesystemSource(...)` | From `@fabric-harness/connectors` | | Arbitrary HTTP | `httpFilesystemSource([{ url, path }])` | Fetch + cache as files | Mount any of these with `withFilesystemSources('virtual', [{ mountAt, source }])` and the agent gets `read` / `grep` / `glob` over them — no embeddings, no retrieval pipeline. --- ## 1. Issue Triage Read a GitHub issue, classify severity, suggest labels, draft a reply. ```ts import { agent } from '@fabric-harness/sdk'; export default agent<{ issueNumber: number; title: string; body: string }>({ name: 'triage', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); return await session.prompt( `Triage issue #${input.issueNumber}: "${input.title}". Return JSON { severity, labels[], suggestedComment }.\n\n${input.body}`, ); }, }); ``` ```ts import { agent, defineCommand, schema } from '@fabric-harness/sdk'; const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } }); export default agent({ name: 'triage', input: schema.object({ issueNumber: schema.number(), title: schema.string(), body: schema.string() }), output: schema.object({ severity: schema.enum(['low', 'medium', 'high', 'critical']), labels: schema.array(schema.string()), suggestedComment: schema.string(), }), triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init({ policy: { commandPolicy: { allow: ['gh issue view*'], requireApproval: ['gh issue comment*'] } }, })).session(); return await session.skill('triage-issue', { args: input, commands: [gh] }); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-issue-triage-ci run:triage` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/issue-triage-ci) --- ## 2. PR Reviewer Review a pull request diff for security / performance / readability and produce structured findings. ```ts import { agent } from '@fabric-harness/sdk'; export default agent<{ prNumber: number; diff: string }>({ name: 'pr-review', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); return await session.prompt( `Review PR #${input.prNumber}. Return JSON array { file, line?, severity, category, message, suggestion? }.\n\n${input.diff}`, ); }, }); ``` ```ts import { agent, defineCommand, schema } from '@fabric-harness/sdk'; const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } }); const finding = schema.object({ file: schema.string(), line: schema.number().optional(), severity: schema.enum(['info', 'low', 'medium', 'high']), category: schema.enum(['bug', 'security', 'performance', 'tests', 'readability']), message: schema.string(), }); export default agent({ name: 'pr-review', input: schema.object({ prNumber: schema.number(), repository: schema.string() }), output: schema.object({ findings: schema.array(finding) }), run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local' })).session(); const diff = await session.shell(`gh pr diff ${input.prNumber} -R ${input.repository}`); return await session.skill('review-diff', { args: { diff: diff.stdout }, commands: [gh] }); }, }); ``` **Run it:** [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/code-review) --- ## 3. Changelog Writer Turn `git log` between two refs into a Keep-a-Changelog section. ```ts import { agent } from '@fabric-harness/sdk'; export default agent<{ from: string; to: string }>({ name: 'changelog', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local' })).session(); const log = await session.shell(`git log --pretty='%h %s' ${input.from}..${input.to}`); return { changelog: await session.prompt( `Format these commits as a Keep-a-Changelog section. Group: Added/Changed/Fixed/Removed.\n\n${log.stdout}`, )}; }, }); ``` ```ts import { agent, schema } from '@fabric-harness/sdk'; export default agent({ name: 'changelog', input: schema.object({ from: schema.string(), to: schema.string() }), output: schema.object({ added: schema.array(schema.string()), changed: schema.array(schema.string()), fixed: schema.array(schema.string()) }), run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local' })).session(); const log = await session.shell(`git log --pretty='%h %s' ${input.from}..${input.to}`); return await session.prompt(`Group commits into added/changed/fixed arrays.\n\n${log.stdout}`); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-changelog-writer run` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/changelog-writer) --- ## 4. Test Generator Generate a Vitest spec for a TypeScript source file. ```ts import { agent } from '@fabric-harness/sdk'; import { readFile } from 'node:fs/promises'; export default agent<{ file: string }>({ name: 'testgen', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); const source = await readFile(input.file, 'utf8'); return { spec: await session.prompt( `Write a thorough vitest spec for the source below. Cover happy/error/boundary paths.\n\n${source}`, )}; }, }); ``` ```ts import { agent, schema } from '@fabric-harness/sdk'; import { readFile } from 'node:fs/promises'; export default agent({ name: 'testgen', input: schema.object({ file: schema.string(), framework: schema.enum(['vitest', 'jest']).optional() }), output: schema.object({ specPath: schema.string(), spec: schema.string() }), run: async ({ init, input }) => { const session = await (await init()).session(); const source = await readFile(input.file, 'utf8'); return await session.prompt( `Write a ${input.framework ?? 'vitest'} spec. Return JSON { specPath, spec }.\n\n${source}`, ); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-test-generator run` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/test-generator) --- ## 5. Dependency Auditor Run `npm/pnpm audit` and prioritize the findings. ```ts import { agent } from '@fabric-harness/sdk'; export default agent<{ manager?: 'npm' | 'pnpm' }>({ name: 'audit', triggers: { schedule: '0 9 * * *' }, run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local' })).session(); const cmd = input.manager === 'pnpm' ? 'pnpm audit --json' : 'npm audit --json'; const result = await session.shell(`${cmd} || true`); return { summary: await session.prompt( `Summarize this audit JSON. List Critical/High first with package, version, recommended upgrade.\n\n${result.stdout.slice(0, 80_000)}`, )}; }, }); ``` ```ts import { agent, schema } from '@fabric-harness/sdk'; const finding = schema.object({ package: schema.string(), severity: schema.enum(['low', 'moderate', 'high', 'critical']), recommendedVersion: schema.string().optional(), }); export default agent({ name: 'audit', input: schema.object({ manager: schema.enum(['npm', 'pnpm']).optional() }), output: schema.object({ findings: schema.array(finding) }), triggers: { schedule: '0 9 * * *' }, run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local' })).session(); const cmd = input.manager === 'pnpm' ? 'pnpm audit --json' : 'npm audit --json'; const result = await session.shell(`${cmd} || true`); return await session.prompt(`Extract findings from audit JSON.\n\n${result.stdout.slice(0, 80_000)}`); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-dependency-auditor run` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/dependency-auditor) --- ## 6. Schema Migration Author Draft a forward + rollback SQL migration for a described change. Mutating apply commands are gated behind approval in the complete entrypoint example. ```ts import { agent } from '@fabric-harness/sdk'; export default agent<{ description: string; dialect?: string }>({ name: 'migrate', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); return await session.prompt( `Draft a ${input.dialect ?? 'postgres'} migration: "${input.description}". Return JSON { filename, upSql, downSql }.`, ); }, }); ``` ```ts import { agent, schema } from '@fabric-harness/sdk'; import type { CapabilityPolicy } from '@fabric-harness/sdk'; const policy: CapabilityPolicy = { commandPolicy: { allow: ['ls migrations*', 'cat migrations/*'], requireApproval: ['psql*', 'pnpm prisma migrate*'], deny: ['rm -rf*'], }, approvals: { requiredApprovals: 1, defaultTimeoutMs: 300_000, risk: 'high' }, }; export default agent({ name: 'migrate', input: schema.object({ description: schema.string(), dialect: schema.enum(['postgres', 'mysql']).optional() }), output: schema.object({ filename: schema.string(), upSql: schema.string(), downSql: schema.string() }), run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local', policy })).session(); const existing = await session.shell('ls migrations 2>/dev/null || true'); return await session.prompt( `Draft a ${input.dialect ?? 'postgres'} migration: "${input.description}". Existing:\n${existing.stdout}`, ); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-schema-migration run` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/schema-migration) --- ## 7. API Docs Generator Generate MDX docs pages for HTTP routes, matching the tone of an existing Fumadocs site. ```ts import { agent, fumadocsSource, withFilesystemSources, localDirectorySource } from '@fabric-harness/sdk'; export default agent<{ sourceDir: string; docsRoot?: string }>({ name: 'apidocs', triggers: { webhook: true }, run: async ({ init, input }) => { const sandbox = withFilesystemSources('virtual', [ { mountAt: '/workspace/api', source: localDirectorySource(input.sourceDir) }, ...(input.docsRoot ? [{ mountAt: '/workspace/kb', source: fumadocsSource(input.docsRoot) }] : []), ]); const session = await (await init({ sandbox })).session(); return { mdx: await session.prompt( 'Read /workspace/api with the read tool. Match tone of /workspace/kb. Return JSON { ".mdx": "" }.', )}; }, }); ``` ```ts import { agent, fumadocsSource, localDirectorySource, schema, withFilesystemSources } from '@fabric-harness/sdk'; export default agent({ name: 'apidocs', input: schema.object({ sourceDir: schema.string(), docsRoot: schema.string().optional() }), output: schema.object({ pages: schema.record(schema.string()) }), run: async ({ init, input }) => { const sandbox = withFilesystemSources('virtual', [ { mountAt: '/workspace/api', source: localDirectorySource(input.sourceDir) }, ...(input.docsRoot ? [{ mountAt: '/workspace/kb', source: fumadocsSource(input.docsRoot) }] : []), ]); const session = await (await init({ sandbox })).session(); return await session.prompt('Generate MDX page per route. Return { pages: { slug: content } }.'); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-api-docs-generator run` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/api-docs-generator) --- ## 8. Bug Reproducer Convert a free-form bug report into a minimal failing test. ```ts import { agent } from '@fabric-harness/sdk'; export default agent<{ issueBody: string; framework?: string }>({ name: 'repro', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init()).session(); return { repro: await session.prompt( `Produce a minimal failing ${input.framework ?? 'vitest'} test for this report:\n\n${input.issueBody}`, )}; }, }); ``` ```ts import { agent, schema } from '@fabric-harness/sdk'; export default agent({ name: 'repro', input: schema.object({ issueBody: schema.string(), framework: schema.enum(['vitest', 'jest']).optional() }), output: schema.object({ testCode: schema.string(), runCommand: schema.string() }), run: async ({ init, input }) => { const session = await (await init()).session(); return await session.prompt( `Produce a minimal failing ${input.framework ?? 'vitest'} test. Return JSON { testCode, runCommand }.\n\n${input.issueBody}`, ); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-bug-reproducer run` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/bug-reproducer) --- ## 9. Release Notes Drafter Customer-facing release notes from merged PRs between two tags. ```ts import { agent } from '@fabric-harness/sdk'; export default agent<{ tag: string; previous: string }>({ name: 'release-notes', triggers: { webhook: true }, run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local' })).session(); const since = (await session.shell(`git log -1 --format=%aI ${input.previous}`)).stdout.trim(); const prs = await session.shell(`gh pr list --state merged --search 'merged:>=${since}' --json number,title,labels --limit 200`); return { notes: await session.prompt( `Write release notes for ${input.tag}. Group: Highlights/New/Improved/Fixed.\n\n${prs.stdout}`, )}; }, }); ``` ```ts import { agent, defineCommand, schema } from '@fabric-harness/sdk'; const gh = defineCommand('gh', { env: { GH_TOKEN: process.env.GH_TOKEN } }); export default agent({ name: 'release-notes', input: schema.object({ tag: schema.string(), previous: schema.string() }), output: schema.object({ highlights: schema.array(schema.string()), new: schema.array(schema.string()), improved: schema.array(schema.string()), fixed: schema.array(schema.string()), }), run: async ({ init, input }) => { const session = await (await init({ sandbox: 'local' })).session(); const since = (await session.shell(`git log -1 --format=%aI ${input.previous}`)).stdout.trim(); const prs = await session.shell(`gh pr list --state merged --search 'merged:>=${since}' --json number,title,labels --limit 200`); return await session.skill('group-prs', { args: { prs: prs.stdout, tag: input.tag }, commands: [gh] }); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-release-notes run` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/release-notes) --- ## 10. Incident Runbook Assistant Match an alert to a mounted runbook and walk through diagnostic steps. Read-only — never executes. ```ts import { agent, fumadocsSource, withFilesystemSources } from '@fabric-harness/sdk'; export default agent<{ alert: string; runbookRoot?: string }>({ name: 'runbook', triggers: { webhook: true }, run: async ({ init, input }) => { const sandbox = withFilesystemSources('virtual', [ { mountAt: '/workspace/runbooks', source: fumadocsSource(input.runbookRoot ?? './runbooks') }, ]); const session = await (await init({ sandbox })).session(); return { plan: await session.prompt( `Alert: "${input.alert}". Grep /workspace/runbooks, read the best match, return root causes + diagnostic commands. Do NOT execute.`, )}; }, }); ``` ```ts import { agent, fumadocsSource, schema, withFilesystemSources } from '@fabric-harness/sdk'; export default agent({ name: 'runbook', input: schema.object({ alert: schema.string(), runbookRoot: schema.string().optional() }), output: schema.object({ matchedRunbook: schema.string(), likelyCauses: schema.array(schema.string()), diagnosticCommands: schema.array(schema.string()), }), run: async ({ init, input }) => { const sandbox = withFilesystemSources('virtual', [ { mountAt: '/workspace/runbooks', source: fumadocsSource(input.runbookRoot ?? './runbooks') }, ]); const session = await (await init({ sandbox })).session(); return await session.prompt(`Alert: "${input.alert}". Match a runbook and return structured causes + commands.`); }, }); ``` **Run it:** `pnpm --filter @fabric-harness/example-incident-runbook run` · [source](https://github.com/Fabric-Pro/fabric-harness/tree/main/examples/incident-runbook) --- ## How they compose These ten aren't isolated. Real workflows combine them: ```mermaid graph LR A[GitHub webhook] --> B[#1 Triage] B -->|severity high| C[#8 Bug Reproducer] C --> D[Failing test in PR] D --> E[#2 PR Reviewer] E -->|approved| F[Merge] F --> G[#3 Changelog] G --> H[Release tag] H --> I[#9 Release Notes] H --> J[#5 Dep Audit] ``` Adjacent stages share the same Fabric Harness primitives — one agent's `output` schema feeds the next agent's `input` schema, and the policy stays consistent across the pipeline. ## Picking an entrypoint | Choose the minimal entrypoint when | Choose the complete entrypoint when | |---|---| | Prototyping, internal tools, edge/serverless deployment | Mutations need approval, you want typed I/O at the boundary | | Inputs are loose JSON | Inputs come from a webhook with a versioned schema | | One agent, one model | Skills, custom roles, custom session stores, telemetry | | Cloudflare Workers, Vercel Edge, Lambda | Temporal worker, Foundry Hosted, Docker fleet | You can mix: an agent using the minimal entrypoint can `init({ policy })` to opt into capability gating without leaving the smaller import graph. Durability still comes from runtime settings, not the import path.