# External Media Orchestration

This note describes how an external service can use Open Design as a creative
runtime while keeping media provider governance outside Open Design.

Open Design contributes project context, skills, design systems, previews,
artifact structure, and design-aware prompt composition. The external service
owns caller auth, admission, templates, fanout, retries, accounting, webhooks,
provider credentials, budgets, model routing, and provider rate limits.

## Boundary

Run-scoped `mediaExecution` controls only Open Design-owned media generation.
It applies to:

- token-gated `/api/tools/media/generate`
- in-run `od media generate` when `OD_TOOL_TOKEN` is present
- Open Design's Codex image generation prompt override
- Open Design's media generation prompt contract

It intentionally does not apply to external MCP media tools. If a run receives
MCP tools from an external service, that service owns the provider policy for
those tools.

Open Design should not grow a generic provider router, provider account pool,
global media budget system, or external executor API unless there is a separate
owner decision for that product surface.

## Recommended Composition

Use HTTP/SSE between the external service and Open Design. Avoid shelling out to
`od` from the external service unless the integration specifically needs the CLI
contract.

1. The external service authenticates the caller and decides provider policy.
2. The external service creates or selects an Open Design project.
3. The external service starts a run with `mediaExecution` set to the desired
   Open Design-owned media policy.
4. The run includes skills and MCP tools that describe the external media
   workflow.
5. Open Design handles design/runtime work. External MCP media tools handle
   provider execution when the external service permits it.
6. The external service stores final provider outputs in the project through
   normal artifact or file APIs, or asks the agent to place returned assets in
   the project workspace.

For runs where the external service owns all provider execution, start with:

```json
{
  "agentId": "codex",
  "projectId": "project_123",
  "conversationId": "conversation_123",
  "message": "Create a campaign concept and produce the requested media through the configured media tools.",
  "mediaExecution": {
    "mode": "disabled"
  }
}
```

For runs where Open Design may use only a narrow part of its own media path,
use allowlists:

```json
{
  "mediaExecution": {
    "mode": "enabled",
    "allowedSurfaces": ["image"],
    "allowedModels": ["gpt-image-2"]
  }
}
```

## Skill Pattern

A skill should describe creative intent and routing expectations. It should not
embed provider credentials or provider-account policy.

Example `SKILL.md` fragment:

```md
# Media Campaign Skill

Use Open Design project context, design-system guidance, and artifact previews
to plan the campaign. When media bytes are needed, use the configured external
media MCP tools. Do not call Open Design-owned media generation unless the run
policy explicitly permits it.

For every generated asset, write a short project note that records:

- creative brief
- requested surface and dimensions
- provider-facing prompt summary
- returned asset path or URL
- usage constraints supplied by the external media tool
```

This keeps the skill focused on design workflow. The external tool remains the
authority for provider execution and fulfillment rules.

## MCP Tool Pattern

MCP is the preferred way to expose external media execution to an agent run
without making Open Design own provider auth or budgets.

A media MCP server can expose tools such as:

- `media.describe_policy`: return available surfaces, model families, budget
  hints, and safety constraints for the current caller
- `media.submit`: submit a provider request and return an external request id
- `media.status`: poll or fetch completion state
- `media.cancel`: cancel a pending request when supported
- `media.attach_result`: return a URL or project-file placement instruction for
  completed media

These names are illustrative. The stable contract belongs to the MCP server and
the external service, not to Open Design core.

The MCP server should receive its own credentials from the external service or
from its deployment environment. Open Design should only see the MCP tool
surface made available to the run.

## Artifact Handoff

Prefer one of these handoff shapes:

- The MCP tool returns a downloadable URL and the agent writes or imports the
  asset into the project.
- The external service uploads the fulfilled asset through an Open Design
  project file/artifact API after provider completion.
- The MCP tool returns metadata that the agent records in a project manifest or
  handoff note, while the external service keeps the provider artifact as the
  source of truth.

Avoid storing provider credentials, account ids, budget ids, or retry policy in
Open Design project files. Project artifacts should describe creative output,
not provider-account governance.

## Legacy Media Endpoint Caveat

`POST /api/projects/:id/media/generate` predates run-scoped media policy. It is
still available for normal Open Design media generation outside the in-run tool
path. The accepted v1 policy closes the cooperative in-run CLI path by routing
`od media generate` through `/api/tools/media/generate` when `OD_TOOL_TOKEN` is
present.

A raw local HTTP call from inside a run can still reach the legacy project route
without presenting the run token. That hardening question is tracked separately
in issue #3199 because it has product decisions around UI compatibility,
outside-run CLI behavior, and whether the legacy route should become
policy-aware or be reserved for non-run callers.

## Non-goals

This composition pattern does not add:

- `request-only` run mode in Open Design core
- Open Design media request persistence for external provider work
- a generic HTTP executor provider inside Open Design
- provider credentials or account pools in Open Design
- Open Design-owned provider budgets, retries, or global media limits
- MCP tool routing based on `mediaExecution.allowedSurfaces` or
  `mediaExecution.allowedModels`

If MCP orchestration needs its own constraints later, add a separate explicit
contract for that surface instead of reusing `mediaExecution`.
