FabricFabricHarness
Databricks

Genie Agent Mode learning path

Create, grant, invoke, govern, test, and clean up a Databricks Genie Agent used through the Beta Agent Mode API.

Harness supports the complete application-side journey around Genie Agent Mode, but two native Databricks concepts must stay distinct:

  • A Genie Agent is the Databricks resource. Harness can create, inspect, query, update, permission, export/import, and trash that resource through DatabricksGenieAdmin.
  • Agent Mode is a Beta response-streaming API for an existing Genie Agent. Harness does not create a second “Agent Mode agent” resource.

The ordinary Genie Conversation API and the Agent Mode SSE API are separate clients. Harness never silently substitutes one for the other.

Choose the path

SituationUse
A Genie Agent already existsConfigure genie.agentMode.agentId, acknowledge Beta, and invoke it.
Harness should manage the resourceConfigure genie.manage, create through genieAdmin, grant the runtime principal, then bind the returned agentId to Agent Mode.
A Harness model should delegate analytics to GenieSet modelTool, register dbx.tools, and retain normal Harness policy and budgets around the call.
The workload runs as a Databricks AppDeclare databricks.app.genie; the build emits a native genie_space resource and injects the ID through valueFrom.

Prerequisites

You need:

  • @fabric-harness/databricks@^7.0.2, @fabric-harness/sdk@^6.1.0, and Node.js 22 or newer;
  • a Databricks workspace enrolled by the account team for the Agent Mode preview;
  • Genie Agents Agent Mode API enabled under workspace Previews;
  • a pro or serverless SQL Warehouse;
  • SELECT on every Unity Catalog table used by the Genie Agent;
  • an authoring principal with workspace-folder and Genie management permission when Harness manages the resource; and
  • a runtime principal with CAN_RUN on the Genie Agent plus access to its warehouse and data.

Use separate authoring and runtime principals in production. The same service principal is acceptable only for a controlled development proof where that broader authority is intentional.

Path 1: invoke an existing Genie Agent

.fabricharness/jobs/revenue-review.ts
import {
  type DatabricksGenieAgentModeResponse,
  databricks,
} from '@fabric-harness/databricks';
import { defineAgent, schema } from '@fabric-harness/sdk';

const host = process.env.DATABRICKS_HOST!;
const revenueIntelligence = databricks({
  host,
  principal: {
    kind: 'service-principal',
    host,
    clientId: process.env.DATABRICKS_RUNNER_CLIENT_ID!,
    clientSecret: process.env.DATABRICKS_RUNNER_CLIENT_SECRET!,
  },
  genie: {
    agentMode: {
      agentId: process.env.DATABRICKS_REVENUE_GENIE_AGENT_ID!,
      acknowledgeBeta: true,
      timeoutMs: 5 * 60_000,
      idleTimeoutMs: 2 * 60_000,
      onEvent: (event) => console.log(event.sequenceNumber, event.type),
    },
  },
});

export default defineAgent({
  name: 'revenue-review',
  input: schema.object({
    question: schema.string(),
    conversationId: schema.string().optional(),
  }),
  output: schema.string(),
  run: async ({ input }) => {
    const revenueReview = await revenueIntelligence.genieAgentMode!.complete(input.question, {
      ...(input.conversationId ? { conversationId: input.conversationId } : {}),
    });
    return JSON.stringify(revenueReview satisfies DatabricksGenieAgentModeResponse);
  },
});

Run it:

export DATABRICKS_HOST=https://<workspace-host>
export DATABRICKS_RUNNER_CLIENT_ID=...
export DATABRICKS_RUNNER_CLIENT_SECRET=...
export DATABRICKS_REVENUE_GENIE_AGENT_ID=0123456789abcdef0123456789abcdef

fh run revenue-review \
  --payload '{"question":"Summarize revenue by region."}'

Expected output includes increasing SSE sequence numbers, a response.completed event, and one bounded terminal response containing a conversationId. Pass that ID on the next invocation to continue the same native conversation. Agent Mode allows one in-flight response per conversation.

What developers see

The application can present the native conversation while preserving the verified caller, ordered SSE sequence, generated SQL step, result evidence, terminal event, and bounded timeout. This representative view is sanitized and does not embed a workspace or user identifier.

RevenueAnalyst Genie Agent Mode conversation showing ordered question, SQL execution, grounded answer, and terminal events
Representative UIHarness preserves the tenant-bound caller, ordered Agent Mode stream, result evidence, terminal event, timeout, and cleanup boundary.

Path 2: manage, grant, invoke, and clean up

The runnable with-databricks-genie-authoring workspace contains this lifecycle. The important handoff is salesAnalyst.agentId: the exact resource returned by management is the resource Agent Mode invokes.

import {
  LakebaseDatabricksManagedResourceStore,
  databricks,
  databricksSdk,
  lakebaseClient,
} from '@fabric-harness/databricks';

const host = process.env.DATABRICKS_HOST!;
const authorClientId = process.env.DATABRICKS_AUTHOR_CLIENT_ID!;
const genieAuthoringPrincipal = {
  kind: 'service-principal' as const,
  host,
  clientId: authorClientId,
  clientSecret: process.env.DATABRICKS_AUTHOR_CLIENT_SECRET!,
};
const genieRunnerPrincipal = {
  kind: 'service-principal' as const,
  host,
  clientId: process.env.DATABRICKS_RUNNER_CLIENT_ID!,
  clientSecret: process.env.DATABRICKS_RUNNER_CLIENT_SECRET!,
};
const genieAuthoringSdk = databricksSdk({ host, principal: genieAuthoringPrincipal });
const genieOwnershipStore = new LakebaseDatabricksManagedResourceStore({
  client: lakebaseClient({
    host: process.env.DATABRICKS_LAKEBASE_HOST!,
    database: process.env.DATABRICKS_LAKEBASE_DATABASE!,
    user: process.env.DATABRICKS_LAKEBASE_USER!,
    endpoint: process.env.DATABRICKS_LAKEBASE_ENDPOINT!,
    credentialClient: genieAuthoringSdk.postgres,
  }),
});
const genieManagement = databricks({
  host,
  principal: genieAuthoringPrincipal,
  genie: {
    manage: {
      resourceStore: genieOwnershipStore,
      delete: 'managed-only',
    },
  },
});

const salesAnalyst = await genieManagement.genieAdmin!.create(
  {
    version: 2,
    title: 'Revenue intelligence analyst',
    parentPath: '/Shared/agents',
    warehouseId: process.env.DATABRICKS_WAREHOUSE_ID!,
    dataSources: [{ table: 'main.sales.orders' }],
    instructions: ['Answer revenue questions using governed data sources.'],
  },
  {
    ownership: {
      principal: process.env.DATABRICKS_AUTHOR_CLIENT_ID!,
      toolCallId: 'revenue-intelligence-bootstrap',
    },
  },
);

try {
  await genieManagement.genieAdmin!.updatePermissions(salesAnalyst.agentId, [
    {
      servicePrincipalName: process.env.DATABRICKS_RUNNER_CLIENT_ID!,
      permissionLevel: 'CAN_RUN',
    },
  ]);

  const revenueIntelligence = databricks({
    host,
    principal: genieRunnerPrincipal,
    genie: {
      agentMode: {
        agentId: salesAnalyst.agentId,
        acknowledgeBeta: true,
        timeoutMs: 5 * 60_000,
        idleTimeoutMs: 2 * 60_000,
      },
    },
  });

  const revenueReview = await revenueIntelligence.genieAgentMode!.complete(
    'Summarize revenue by region.',
  );
  console.log(revenueReview.output);
} finally {
  await genieManagement.genieAdmin!.delete(salesAnalyst.agentId);
}

Production resource management should use a durable DatabricksManagedResourceStore; the provided Lakebase implementation survives process replacement. Model-facing management additionally requires a steward audience, catalog allowlist, exact-operation approval, and a server-side sqlPolicy before SQL-bearing configuration becomes available.

Path 3: expose Agent Mode as a governed model tool

modelTool constructs databricks_genie_agent_mode, but the Harness definition must register revenueIntelligence.tools:

.fabricharness/jobs/revenue-briefing.ts
import { databricks } from '@fabric-harness/databricks';
import { defineAgent, schema } from '@fabric-harness/sdk';

const host = process.env.DATABRICKS_HOST!;
const genieRunnerPrincipal = {
  kind: 'service-principal' as const,
  host,
  clientId: process.env.DATABRICKS_RUNNER_CLIENT_ID!,
  clientSecret: process.env.DATABRICKS_RUNNER_CLIENT_SECRET!,
};
const revenueIntelligence = databricks({
  host,
  principal: genieRunnerPrincipal,
  model: 'system.ai.gpt-oss-20b',
  genie: {
    agentMode: {
      agentId: process.env.DATABRICKS_REVENUE_GENIE_AGENT_ID!,
      acknowledgeBeta: true,
      modelTool: { maxOutputBytes: 512 * 1024 },
    },
  },
});

export default defineAgent({
  name: 'revenue-briefing',
  input: schema.object({ question: schema.string() }),
  output: schema.string(),
  init: {
    modelProvider: revenueIntelligence.modelProvider,
    tools: revenueIntelligence.tools,
    policy: revenueIntelligence.policy,
  },
  run: ({ input, prompt }) =>
    prompt(`Use the governed Genie tool to answer this revenue question: ${input.question}`),
});

Progress events go only to the server-side onEvent callback. The model receives one bounded terminal response, not the entire SSE stream.

Bind the resource to a Databricks App

.fabricharness/config.ts
export default {
  target: 'databricks-app',
  databricks: {
    app: {
      genie: {
        agentId: process.env.DATABRICKS_REVENUE_GENIE_AGENT_ID,
        permission: 'CAN_RUN',
      },
    },
  },
};

The build writes a native genie_space resource and injects DATABRICKS_GENIE_AGENT_ID through valueFrom; the ID is not embedded in the model prompt. CAN_EDIT and CAN_MANAGE require the App binding to opt into authoring.

Failure behavior

FailureHarness behaviorNext action
404 FEATURE_DISABLEDFails explicitly; ordinary Genie is not substituted.Ask the account team to enroll the workspace and enable the preview.
403Surfaces native permission denial without exposing credentials.Grant the runtime principal and verify warehouse/data access.
409Reports the one-in-flight-response conflict.Wait for or cancel the active response; do not retry concurrently.
Idle or overall deadlineAborts the request and raises DatabricksGenieAgentModeTimeoutError.Inspect progress evidence and choose a bounded workload-specific deadline.
Caller cancellationPropagates the abort signal to the response stream.Record the cancelled terminal state; do not convert it to a retry.
Malformed or out-of-order SSERaises DatabricksGenieAgentModeProtocolError.Retain redacted evidence and check the Beta API rollout/version.
response.failedYields the terminal event, then raises DatabricksGenieAgentModeResponseError.Inspect the bounded native error and correct the question, data, or agent configuration.
Stream ends without a terminal eventFails the protocol contract.Treat the outcome as unknown; do not claim completion.

Response-creating POSTs are never automatically retried because an ambiguous response could duplicate work. Event size, event count, model-tool output, idle time, and total time are bounded.

Cleanup

  • A managed test lifecycle must trash the exact Genie Agent in a finally block.
  • Harness waits until exact-ID reads show the resource is no longer active before removing its durable ownership record.
  • Repeating deletion after the resource moved to trash is an idempotent no-op.
  • Do not delete a pre-existing production Genie Agent merely because one Agent Mode conversation finished; retain or delete it according to its owner and lifecycle policy.

Test and certification boundary

Run the local protocol and composition tests without workspace credentials:

pnpm --filter @fabric-harness/databricks test -- genie-agent-mode
pnpm --filter @fabric-harness/example-with-databricks-genie-authoring test
pnpm --filter @fabric-harness/example-with-databricks-genie-authoring build

The current 7.0.2 protected record establishes stable Genie Agent create/query/update/trash authoring and separately records a passing optional Tier O Agent Mode streaming probe in Azure eastus2. It does not promote the Beta API to stable, establish Agent Mode authoring as a separate resource lifecycle, prove the 14-day rolling claim, or certify AWS/GCP workspaces. Read the exact certification record before making a customer claim.