Databricks Solutions Accelerator

A deployable Databricks project that routes tracks through MCP, A2A or the REST API with a shared audit trail.

The Spatial9 Solutions Accelerator for Databricks is a deployable Databricks Asset Bundle that turns a music catalog stored in Unity Catalog into immersive audio on a schedule. Every render is governed, idempotent and audited. The bundle contains six notebooks and reaches Spatial9 through three routes: the MCP server, the A2A agent and the Spatializer REST API.

This page is for data engineers and platform teams who run Databricks and want a production starting point for catalog-scale spatial audio. It describes the architecture, the tables and Volumes the accelerator creates, the deploy procedure, how to route a track, the governance controls and how to expose Spatial9 to Mosaic AI agents. The files live in the Agent Skills repository under examples/enterprise/databricks/solution-accelerator, which you can download from /agents/skills.

Architecture

New masters land in a Volume. Auto Loader records each file once in a bronze table, and a MERGE adds it to the render queue, a Delta table that is the source of truth. Each queue row carries a route that selects the integration. Finished files are written to the immersive Volume, every state change is appended to an audit table, and gold views summarize coverage, throughput and failures.

masters Volume --Auto Loader--> bronze_masters --MERGE--> render_queue (Delta, source of truth)
                                                              |
             route = mcp                    route = a2a                    route = api
       03 Spatial9 MCP server        04 Spatial9 A2A agent         05 Spatializer REST API
  create_spatial_audio,             split_stems, convert_dolby,   stems -> Dolby Atmos + IAMF
  get_job_status, get_downloads     preview_chorus (free)         (tus resumable upload)
                                                              |
                    immersive Volume  +  render_events (audit)  -->  06 gold views

Choosing a route

The route column decides which notebook processes a row. All three routes write to the same Volume and the same audit table.

RouteUse it whenWhat comes back
mcpYou have a stereo master and want the full Spatial9 renderEclipsa Audio (IAMF), YouTube-ready MP4, binaural headphone WAV
a2aYou want to hand a whole task to the Spatial9 agent and follow itStems as WAV, Dolby or ADM converted to Eclipsa Audio, or a free 30-second chorus preview
apiYou already have stems and need delivery mastersDolby Atmos master and IAMF from the Spatializer REST API

MCP and A2A use the same s9_ key and share the plan limits: 3 renders at once and 20 per day. Failed renders are refunded automatically. Chorus previews are free and do not count toward the render limit. The REST API route uses a separate key, spatializer_api_key.

Bundle contents

FilePurpose
databricks.ymlAsset Bundle: one job, five tasks, every 15 minutes (paused by default)
00_config.pyWidgets, table names, limits and helpers shared by every notebook
01_setup.pySchema, four Volumes, queue and audit tables, optional governed HTTP connection
02_ingest_masters.pyAuto Loader picks up new masters and queues them once
03_render_mcp.pyFull immersive renders through the MCP server
04_delegate_a2a.pyStems, Dolby or ADM conversion and chorus previews through the A2A agent
05_atmos_rest_api.pyStems to Dolby Atmos and IAMF masters with resumable uploads
06_gold_views.sqlCoverage, daily throughput and failure reasons for dashboards or Genie
../../shared/spatial9_mcp.pyMCP client built on requests only
../../shared/spatial9_a2a.pyA2A client built on requests only

Requirements

  • A Databricks workspace with Unity Catalog and the Databricks CLI configured.
  • A SQL warehouse, whose ID you pass as warehouse_id for the gold views task.
  • A Spatial9 personal key (s9_...) created at /connect, used for the MCP and A2A routes.
  • A Spatializer REST API key, only if you use the api route.
  • Masters in WAV, FLAC or AIFF format that you own or license.

Deploy in five steps

  1. Create a key at /connect and store it in a secret scope with databricks secrets create-scope spatial9 and then databricks secrets put-secret spatial9 api_key. For the REST API route, also store spatializer_api_key.
  2. Download the Agent Skills repository from /agents/skills and open a terminal in the solution-accelerator folder.
  3. Deploy the bundle with databricks bundle deploy -t dev --var warehouse_id=<your SQL warehouse id>.
  4. Run 01_setup.py once from the workspace, then copy a few WAV files into the masters Volume.
  5. Run the job with databricks bundle run spatial9_accelerator -t dev. When the first renders look correct, unpause the schedule.
databricks secrets create-scope spatial9
databricks secrets put-secret spatial9 api_key              # s9_... key (MCP and A2A)
databricks secrets put-secret spatial9 spatializer_api_key  # only for the REST API route
databricks bundle validate
databricks bundle deploy -t dev --var warehouse_id=<your SQL warehouse id>
databricks bundle run spatial9_accelerator -t dev

Job definition

The bundle defines one job named Spatial9 immersive catalog. It runs every 15 minutes in UTC, starts in the PAUSED state and allows only one concurrent run, which keeps the 3-renders-at-once limit simple to enforce. The bundle syncs the shared folder so the notebooks can import both clients.

resources:
  jobs:
    spatial9_accelerator:
      name: Spatial9 immersive catalog
      max_concurrent_runs: 1
      schedule:
        quartz_cron_expression: "0 0/15 * * * ?"
        timezone_id: UTC
        pause_status: PAUSED
      tasks:
        - task_key: ingest
          notebook_task: { notebook_path: ./02_ingest_masters.py }
        - task_key: render_mcp
          depends_on: [{ task_key: ingest }]
          notebook_task: { notebook_path: ./03_render_mcp.py }
        - task_key: delegate_a2a
          depends_on: [{ task_key: render_mcp }]
          notebook_task: { notebook_path: ./04_delegate_a2a.py }
        - task_key: atmos_rest_api
          depends_on: [{ task_key: ingest }]
          notebook_task: { notebook_path: ./05_atmos_rest_api.py }
        - task_key: gold
          depends_on: [{ task_key: delegate_a2a }, { task_key: atmos_rest_api }]
          notebook_task:
            notebook_path: ./06_gold_views.sql
            warehouse_id: ${var.warehouse_id}

The bundle exposes four variables. Change them with --var at deploy time or through the notebook widgets.

VariableDefaultMeaning
catalogmainUnity Catalog catalog
schemaspatial9_acceleratorSchema for tables and Volumes
secret_scopespatial9Secret scope holding the keys
warehouse_idnoneSQL warehouse for the gold views

The bundle has two targets: dev (development mode, the default) and prod (production mode).

Storage layout

01_setup.py creates the schema and four Volumes. 00_config.py validates the catalog and schema names against a plain identifier pattern before they are used in SQL.

VolumePathContents
masters/Volumes/<catalog>/<schema>/mastersWAV, FLAC or AIFF masters to render
stems/Volumes/<catalog>/<schema>/stemsOne folder per track for the REST API route
immersive/Volumes/<catalog>/<schema>/immersiveFinished files
checkpoints/Volumes/<catalog>/<schema>/checkpointsAuto Loader checkpoints

The render queue

render_queue holds one row per track, version and route. It is clustered by status and route, and two constraints restrict route to mcp, a2a or api and status to pending, running, completed or failed.

ColumnTypeNotes
track_idSTRINGRequired; derived from the file name on ingest
versionINTRequired; part of the idempotency key
routeSTRINGmcp, a2a or api
skillSTRINGFor example spatial_audio, split_stems, convert_dolby, preview_chorus, atmos_master
titleSTRINGRequired
source_pathSTRINGFile or stems folder in a Volume
styleSTRINGlive_concert, club, cinema or intimate_studio
instrumentsSTRINGREST API route only: JSON of instrument UUID to stem file names
statusSTRINGRequired
job_idSTRINGSpatial9 job, A2A task or music ID
stageSTRINGLatest reported stage
outputsSTRINGJSON of file ID to Volume path
errorSTRINGPlain-language reason from Spatial9
updated_atTIMESTAMPLast change

The audit table

render_events is an append-only audit trail with the columns track_id, version, route, job_id, event, detail and at. The log() helper writes one row per state change, such as started, preview, completed or failed.

Notebook behavior

02 Ingest

Auto Loader reads the masters Volume as binaryFile, filtered to *.{wav,flac,aif,aiff}, and drops the file content so only metadata is stored. It uses trigger(availableNow=True), so each run processes new files and then stops. A MERGE then inserts version 1 of every new track as a pending mcp row with skill spatial_audio and style live_concert. Re-running the cell never duplicates rows.

03 Render with MCP

The notebook first follows running renders with get_job_status. Completed jobs are downloaded through get_downloads, keeping binaural, iamf and youtube_video, and saved to the immersive Volume. Failed or cancelled jobs are marked failed and logged. It then starts new renders, up to the number returned by free_slots(), by creating an upload link with create_upload_link, uploading the file and calling create_spatial_audio with include_binaural set to true. If Spatial9 reports a limit, the loop stops and waits for the next scheduled run.

job = s9.call("create_spatial_audio", {
    "upload_id": upload(row.source_path),
    "media_type": "music",
    "title": row.title,
    "style": row.style or "live_concert",
    "include_binaural": True,
    "idempotency_key": f"databricks-{row.track_id}-v{row.version}",
})

04 Delegate with A2A

Rows with route = 'a2a' are sent to the Spatial9 A2A agent with message/send. The messageId is the idempotency key and follows the pattern databricks-<skill>-<track>-v<version>. Supported skills are split_stems, convert_dolby and preview_chorus. The notebook uses the MCP client only to create private upload links with the same key. Running tasks are followed with tasks/get. The input-required state is recorded as a failure with the message that Spatial9 needs a file, so check source_path.

05 Dolby Atmos and IAMF with the REST API

For labels that already have stems, each api row points source_path at a folder in the stems Volume and sets instruments to a JSON map of instrument UUID to stem file names. Run the second cell to list instrument IDs from GET /instruments. The notebook creates a music with POST /musics?formats=atmos,iamf, uploads each stem (and an optional file whose name starts with stereo) with tus resumable uploads in 32 MB chunks, then polls GET /musics/{id}/download?format=atmos and format=iamf. A master is ready when the download answers 200. The base URL is https://api.spatializer.spatial9.ai/api/v0.0.1 and the key is sent as-is in the Authorization header. See REST API.

06 Gold views

The SQL notebook creates three views for an AI/BI dashboard or a Genie space.

ViewShows
gold_catalog_coverageTracks per route and skill, counts per status and pct_done
gold_daily_throughputRenders started and completed per day, median minutes, remaining daily capacity
gold_failure_reasonsPlain-language failure reasons, most common first

Sending a track to a different route

Insert a queue row with the route and skill you want. The examples below request a free chorus preview through A2A and Dolby Atmos plus IAMF masters from stems through the REST API.

INSERT INTO main.spatial9_accelerator.render_queue (track_id, version, route, skill, title, source_path, status, updated_at)
VALUES ('night_drive', 1, 'a2a', 'preview_chorus', 'Night Drive', '/Volumes/main/spatial9_accelerator/masters/night_drive.wav', 'pending', current_timestamp());

INSERT INTO main.spatial9_accelerator.render_queue (track_id, version, route, skill, title, source_path, instruments, status, updated_at)
VALUES ('night_drive', 1, 'api', 'atmos_master', 'Night Drive', '/Volumes/main/spatial9_accelerator/stems/night_drive',
        '{"<bass uuid>": ["BASS.wav"], "<drums uuid>": ["DRUMS.wav"], "<vocals uuid>": ["LEAD VOCALS.wav"]}', 'pending', current_timestamp());

To render a new version of an existing track, insert a row with a higher version. The new version produces a new idempotency key and therefore a new render.

Governance controls

  • The key lives only in a secret scope and Databricks redacts it in notebook output.
  • Every start uses an idempotency key (databricks-<track>-v<version>), so retries never render or charge twice.
  • free_slots() respects 3 renders at once and 20 per day before anything starts; chorus previews are excluded from the count.
  • render_events records every start, completion and failure for audit.
  • Queue updates use parameterized SQL, so titles or errors containing quotes never break a statement.
  • Only render music you own or license.

Agents on Databricks

01_setup.py also creates an optional governed HTTP connection named spatial9_mcp that points at https://spatial9.ai on port 443 with base path /mcp and reads the bearer token from the secret scope. Agents and SQL users can then reach Spatial9 without seeing the key.

CREATE CONNECTION IF NOT EXISTS spatial9_mcp TYPE HTTP
OPTIONS (host 'https://spatial9.ai', port '443', base_path '/mcp', bearer_token secret('spatial9', 'api_key'));
GRANT USE CONNECTION ON CONNECTION spatial9_mcp TO `catalog-engineers`;

Register the connection as an external MCP server in Unity AI Gateway (MCP Services). Mosaic AI agents can then call Spatial9 tools under their own identity, governed by USE CONNECTION grants.

Troubleshooting

  • The job runs but nothing starts: the schedule is paused by default, or free_slots() returned 0 because 3 renders are running or 20 have started in the last 24 hours.
  • A row stays pending with a limit error: the daily limit was reached; the next scheduled run retries it.
  • A2A row fails with "Spatial9 needs a file": the task entered input-required; check that source_path points to a readable file.
  • API row never completes: the download endpoint has not answered 200 for both atmos and iamf; the stage column shows the latest status JSON.
  • Spatial9 rejected the key: the secret is wrong or revoked; create a new key at /connect and update the secret scope.

Next steps

Go deeper on the blog