Back to Dev Blog
The Publishing Agent
·
aiengineeringagents

The Publishing Agent

An AI agent that auto-drafts publishing metadata using citation-backed web research, strict evidence gating, and deterministic share math.

ONCE Engineering


Music publishing metadata is tedious to fill out and easy to get wrong. Every track needs composer names, publisher names, PRO affiliations, CAE/IPI numbers, ownership shares, and recording identifiers, all formatted to a specific spec for Unison registration. A single missing field or bad share split can delay royalty collection. We built an AI agent that drafts this automatically using the metadata artists already provide, backed by web research when gaps exist.

Where It Sits

The Publishing Agent lives inside the publishing submission modal on the Release page. When you open the modal for a track, the agent runs automatically if no draft exists yet. It reads the release and track metadata (title, artist, writers, publishers, ISRC) and produces a complete set of Unison publishing fields.

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Release +      │────▶│  Publishing     │────▶│  Unison Field   │
│  Track Metadata │     │  Agent (GPT)    │     │  Map            │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                              │
                        ┌─────┴─────┐
                        │           │
                   ┌────▼───┐ ┌────▼────┐
                   │  Web   │ │ Audit   │
                   │ Search │ │ Log     │
                   └────────┘ └─────────┘

The agent doesn't just map fields. It researches writers and publishers online, validates what it finds against what the user provided, and only applies changes it can back with citations.

User Data Is Source of Truth

The most important design decision: user-provided writer and publisher data is always authoritative. If an artist says their writers are "Jane Doe" and "John Smith," the agent won't default to automatically replace those names with ones it found in a random web search.

The agent can override user-provided writer names, but only under strict conditions:

  • Cover or public domain tracks: Override requires HIGH or VERY_HIGH confidence, concrete disambiguation (the agent must confirm it's referencing the correct version of the song), and at least 2 authoritative citations.
  • Original tracks: Override requires VERY_HIGH confidence and at least 3 authoritative citations from multiple sources.

If the evidence doesn't clear these thresholds, the agent converts the proposed correction into a suggestion instead of applying it. The user sees the suggestion in the modal and can accept or ignore it.

{
  "id": "writer_override_suggestion_1",
  "title": "Possible Writer Correction (Composer 1)",
  "reason": "Alternative writer credits found, but confidence was not high enough to auto-apply.",
  "confidence": "medium",
  "fields": {
    "composer_1_name": "DOE, JANE ELIZABETH",
    "composer_1_affiliation": "ASCAP"
  },
  "citation_urls": [
    "https://www.ascap.com/repertory/..."
  ]
}

This keeps the agent useful without being dangerous. It fills gaps aggressively but defers to the human on anything it's not certain about.

Citation-Backed Web Research

The agent uses OpenAI's web search tool to look up writer affiliations, publisher relationships, CAE/IPI numbers, and PRO memberships. Web search is mandatory by default; the agent must execute at least one search query before returning results.

Every piece of externally sourced data gets evidence-gated:

  1. The model tags each external field with an evidence status: solid, weak, or none
  2. The post-processing layer checks these tags against the citation list
  3. Fields with solid evidence and backing citations are applied to the draft
  4. Fields with weak or none evidence are stripped from the draft and converted into reviewable suggestions

This means the agent never silently inserts unverified data into the publishing form. If it found something online but isn't confident, you'll see it as a suggestion card with the source URLs, not as a pre-filled field.

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  AI Output  │────▶│  Evidence   │────▶│  Applied    │
│  (all       │     │  Gating     │     │  Fields     │
│   fields)   │     │  Layer      │     │             │
└─────────────┘     └──────┬──────┘     └─────────────┘
                           │
                    ┌──────▼──────┐
                    │  Stripped   │
                    │  → Suggest- │
                    │    ions     │
                    └─────────────┘

The result object includes a sourceMode flag that tells the UI how much external data was used:

  • user_only: No web search data made it into the fields
  • citation_backed: External data was applied, all backed by citations
  • weak: Some external data was found but stripped due to insufficient evidence

Deterministic Share Math

Publishing shares are one of the trickiest parts of the form. Unison requires that total ownership shares sum to exactly 100% across all composers and publishers. The rules are specific:

  • No explicit publishers: Composers split 100% of shares
  • Explicit publishers present: Composers split 50%, publishers split 50%

The agent handles this deterministically after the AI model returns its output. We don't trust the model to do share math (who would?). It runs as a post-processing step with exact arithmetic:

// Determine share allocation
const composerTotalShare = hasExplicitPublishers ? 50 : 100;
const publisherTotalShare = hasExplicitPublishers ? 50 : 0;

If writers have share_percent values from the user, those are scaled proportionally to fit within the allocated total. If no shares are specified, they're distributed evenly. A final rounding correction ensures the last slot absorbs any floating-point drift so the total is always exactly 100%.

This two-phase approach (AI for semantic understanding, deterministic code for arithmetic) avoids the class of bugs where a model returns shares that add up to 99.7% or splits 50/50 when it should be 100/0.

Streaming and Live Progress

The agent streams its progress to the UI in real time. As the model works, the modal shows:

  1. Response created: the model has started processing
  2. Web search in progress: the model is searching the internet
  3. Web search searching / completed: search queries executing and finishing
  4. Reasoning summary deltas: the model's reasoning streams in as it thinks
  5. Response completed: final output ready

The reasoning summary is particularly useful. As the model researches and reasons about writer credits, you can see its thought process updating live in the modal. This makes the 5-10 second wait feel productive instead of opaque.

If the model's initial response doesn't include web search results (and web search is required), the agent automatically retries with stricter instructions demanding research execution. This ensures the user always gets citation-backed results when the system is configured to require them.

Formatting and Transforms

Every field value passes through a transform pipeline before reaching the form:

  • Names: Uppercased, diacritics stripped, punctuation removed (José GarcíaJOSE GARCIA)
  • Shares: Percentage signs stripped, comma used as decimal separator (50.5%50,5)
  • Capacities: Normalized to standard codes (CA for composer/author, E for publisher)
  • ISRCs and ISWCs: Validated format, passed through unchanged

Fields that don't match a known publishing field key are silently dropped. The agent can never write to a field that doesn't exist in the schema, same pattern as the Edit Agent.

Audit Trail

Every publishing agent run creates two audit records:

Audit Events

A fail-closed audit logger writes every major action point: request received, agent started, agent skipped (already populated), agent blocked (submission finalized), response received, validation complete, fields persisted, and failures. If the audit write fails, the entire request is blocked. We'd rather reject a request than process it without a record.

Run History

A best-effort logger writes to our backend DB as well, with the full details of each run: the generated fields, suggestions, citations, reasoning summary, override decisions, validation results, and token usage. This never blocks the user request. If it fails, the run still completes. But it gives us complete observability into what the agent produced and why.

The combination means we have both a strict action log (what happened) and a detailed result log (what the agent thought and produced). When a user questions why a field was filled a certain way, we can trace the full chain from input metadata through web search to final output.

How It Fits in the Pipeline

The Publishing Agent is the newest addition to the ONCE agent pipeline.

Like the other agents, the Publishing Agent is self-contained. It doesn't coordinate with the Validation Agent or the Edit Agent. It receives track metadata, produces publishing fields, and returns. The modal handles the rest — displaying suggestions, letting the user edit fields, and submitting the final CSV to the publisher.

What's Next

We're exploring running the Publishing Agent earlier in the flow, during the chat conversation rather than only when the user opens the publishing modal. If the agent can pre-fill publishing data as writers are being discussed, the modal opens with a complete draft instead of a loading state. We're also looking at learning from accepted and rejected suggestions to tune the evidence gating thresholds over time.

The Publishing Agent runs inside the publishing modal on every release. Click auto-fill and it handles the rest.