返回文章

Build Notes

媒体生成 Harness:SKILL.md 与 YAML 契约

把前两篇的结论落成 coding agent 可执行的 SKILL.md 和可评审的 harness.yaml:先授权再花钱、重试不 fork 账单、先对账再重试。

这一篇是前两篇的产物:一份给 coding agent 的 SKILL.md,和一份可评审的 harness.yaml。

它们不是 DeepSeek 原文,而是把原型里的控制面翻译到媒体生成场景后的可执行约束;可以直接复制到项目里,也可以当作 PR 检查表。

这一篇是两份可直接复制的文件:一份给 coding agent 的 SKILL.md,一份可评审的 harness.yaml。它们把前两篇的结论变成约束,而不是再讲一遍道理。

第 3 篇 · 可复用资产

把结论落成一份 Skill

下面这份 SKILL.md 不是 DeepSeek 的原文,而是我把原型里可复用的控制面翻译到媒体生成场景后的产物。它给 coding agent 的不是「多一个角色」,而是一组建站时必须守住的约束:先授权再花钱、一个请求一个 ticket、上下文不扩权、重试不 fork 账单、先记账再做副作用、先对账再重试。

用法:复制到 ~/.codex/skills/media-generation-harness/SKILL.md,或者放到项目的 .agents/skills/。当任务涉及生成任务、provider adapter、credits/billing、retry/reconciliation、queue、moderation、asset delivery 时,它会触发。

outputs/deepseek-super-harness/SKILL.md327 行
展开完整 SKILL.md(可复制)
---
name: media-generation-harness
description: Design, build, or audit a durable backend for an AI image/video generation product. Use when the task involves generation jobs, provider adapters, credits and billing, retries and reconciliation, queues, moderation gates, asset delivery, or turning a prototype into a production media site.
---

# Media Generation Harness

Use this skill when building or reviewing the control plane behind an AI image
or video product. The goal is not "an agent that can call a model". The goal is
a system that spends as little user attention and money as possible per
delivered asset, and can prove what it did after a crash.

## When to use

- A generation request must survive restarts, provider timeouts and duplicate
  webhooks.
- Users pay credits, so authorization, reservation, settlement and refund must
  be auditable.
- More than one provider or model will be tried behind one product surface.
- The UI needs progress and failure states without streaming raw logs.
- A prototype works locally but cannot safely take production traffic.

Do not use this skill to design prompt content, model quality benchmarks or
creative direction. Those are separate jobs.

## Non-negotiable invariants

1. **Authorize before spend.** No provider call happens before ownership,
   quota, price and policy checks pass.
2. **One immutable request, one ticket.** A client retry must resolve to the
   same job, not a second charge.
3. **Deterministic gate before model judgement.** Auth, credits, catalog,
   resolution, duration, rate limit and pre-moderation are code, not prompts.
4. **Context never expands scope.** A later chat message may add creative
   context, but it cannot raise budget, resolution, duration or provider scope.
   Scope changes need a new mandate.
5. **One ticket, many attempts.** A provider retry creates a new attempt under
   the same job. Never fork billing or user-visible identity on retry.
6. **Persist before effect.** Reserve credits, write the outbox row and record
   the provider request before performing the external action.
7. **Reconcile before retry.** If provider state is unknown, query it. Never
   resubmit a job that may already be running.
8. **Exception-only UI.** Show "needs input", "ready to review" and "failed
   with one action". Healthy work is quiet.
9. **The watchdog may stop or downgrade, never expand.** It cannot increase
   budget, resolution, duration or permissions.
10. **Price, catalog and safety policy are versioned.** Every quote and mandate
    records the version it relied on.

## Planes

| Plane | Responsibility | Must not do |
| --- | --- | --- |
| Control | intake, quote, gate, authorize, schedule, settle, notify | run GPU work |
| Execution | submit to provider, poll, download, validate, post-process | decide price or permission |
| Data | jobs, attempts, ledger, assets, events, policy versions | hide side effects in memory |

Treat the control plane as the only writer of job state. Workers report facts;
they do not mutate the database directly.

## Domain model

| Concept | Media-site meaning | Rule |
| --- | --- | --- |
| Source | Web upload, API, Stripe, moderation callback | Normalize and dedupe by source event id |
| Trigger | One immutable request or callback | Stored before processing |
| Thread | Project or conversation | Holds context; does not grant budget |
| Gate | Auth, credits, quota, catalog, cost ceiling, pre-moderation | Deterministic and synchronous |
| Assessment | Prompt clarity, reference suitability, policy risk | Advisory; cannot set price or permission |
| Decision | accept, clarify, reject, defer | `clarify` asks exactly one question |
| Mandate | Model, resolution, duration, max cost, fallback list, rights | Immutable; wider scope means a new mandate |
| Ticket | One billable generation job | Owns user-visible identity and ledger |
| Assignment | One provider attempt | Retries create a new assignment |
| Executor | Provider adapter or GPU worker | Isolated workspace and asset prefix |
| Subscription | Project/thread context | Context only; no new authority |
| Channel | SSE/WebSocket, webhook, email | Ordered per ticket |
| Outbox | Charge, refund, webhook, email, publish | Record first, send after commit |
| Policy | Model catalog, price table, safety rules | Versioned |
| Watchdog | Stuck, overbudget and loop detection | External control loop |

## Job state machine

```text
draft
  -> quoted
  -> authorized
  -> queued
  -> submitted
  -> running
  -> downloaded
  -> validated
  -> ready
  -> delivered

any active state -> needs_input
any active state -> blocked
any active state -> failed
any active state -> canceled
quoted/authorized -> expired
```

Rules:

- `authorized` is the only state from which a provider may be called.
- `submitted` and `running` must carry a provider job id and the exact request
  payload hash.
- `failed` must carry a normalized error code, not only a provider string.
- `canceled` must record whether the provider acknowledged cancellation.
- Terminal states are immutable except for append-only audit events.

## Money path

1. Quote from the current price policy version.
2. Reserve credits with an idempotency key derived from the ticket.
3. Commit the job and outbox row in one transaction.
4. Submit to the provider only after commit.
5. On definite submit failure, release the reserve.
6. On success, settle the actual amount; release the difference.
7. On failure, apply the published refund rule, not an ad-hoc decision.
8. Reconcile provider usage and ledger daily.

Never charge from a webhook handler without checking the stored job and
attempt. Webhooks are delivery mechanisms, not sources of truth.

## Provider adapter contract

```ts
export interface ProviderAdapter {
  readonly id: string

  quote(input: NormalizedRequest, policy: PolicyVersion): Promise<Quote>

  submit(input: {
    request: NormalizedRequest
    attemptId: string
    idempotencyKey: string
    callbackUrl: string
  }): Promise<{
    providerJobId: string
    acceptedAt: string
    raw: unknown
  }>

  get(providerJobId: string): Promise<{
    state: "queued" | "running" | "succeeded" | "failed" | "canceled" | "unknown"
    progress?: number
    output?: OutputRef[]
    error?: { code: string; message: string; retryable: boolean }
    raw: unknown
  }>

  cancel(providerJobId: string): Promise<{
    accepted: boolean
    state: "canceled" | "running" | "unknown"
  }>

  parseWebhook(headers: Headers, body: unknown): Promise<{
    providerJobId: string
    eventId: string
    state: string
    raw: unknown
  }>
}
```

Adapters must normalize provider states, preserve raw payloads for audit and
never expose provider URLs as permanent assets.

## Minimum data model

- `generation_requests`: client idempotency key, owner, raw input hash.
- `quotes`: policy version, line items, currency, expiry.
- `jobs`: ticket id, mandate, state, current attempt id.
- `attempts`: provider, provider job id, request hash, response, timings.
- `credit_ledger`: append-only reserve, settle, release, refund entries.
- `outbox`: unique `(ticket_id, effect_type, effect_key)`, attempts, sent time.
- `provider_events`: unique `(provider, event_id)`, raw payload, processed time.
- `assets`: storage key, checksum, dimensions, duration, moderation result.
- `policy_versions`: catalog, price table, safety rules, effective time.

Use unique indexes for idempotency. A retry that violates one should return the
existing record, not an error the client retries again.

## Gates

Run these in order and stop at the first failure:

1. Authentication and ownership.
2. Request shape and size.
3. Model, resolution and duration allowed by the catalog.
4. Credit balance and per-user/per-IP quota.
5. Cost ceiling for this request.
6. Pre-moderation on prompt and reference assets.
7. Provider availability and fallback eligibility.

Return a stable error code and one actionable next step. Do not return a wall
of provider text.

## Assessment

Use a model only for genuinely semantic questions:

- Is the prompt specific enough to generate the requested asset?
- Is the reference image suitable for the requested edit?
- Does the request appear to violate a policy that the deterministic filter
  cannot express?

The assessment returns `accept`, `clarify` or `reject`, with evidence. It never
sets price, credits, resolution or provider permission.

## Recovery

On startup:

1. Find jobs in `submitted` or `running`.
2. Query the provider for each known provider job id.
3. If the provider has a result, continue from that result.
4. If the provider has no record and the attempt is past its submit deadline,
   mark the attempt failed and allow a new attempt.
5. Never resubmit solely because the local process restarted.
6. Release or settle reserves only after reconciliation.

Store enough provider request context to explain what was sent, but never store
provider credentials in job payloads or prompts.

## Watchdog

Start with deterministic checks; add a model only when the checks cannot
express the failure.

| Check | Action |
| --- | --- |
| Job older than p99 for its model | query provider, then cancel or escalate |
| No provider state change for N minutes | query, then retry with fallback |
| Cost exceeds mandate | stop, release reserve, alert |
| Same error three times | open a provider incident, stop retrying |
| Queue wait exceeds SLO | add capacity or degrade catalog |
| Duplicate webhook storm | dedupe, alert, do not create jobs |

The watchdog has a read-only view of policy and a narrow write set: cancel,
retry within the existing mandate, release reserve, open incident. It cannot
raise budget or permissions.

## Human attention budget

Expose only four user-facing states:

- **Working**: progress without logs.
- **Needs you**: one question, one input.
- **Ready**: preview, download, accept or regenerate.
- **Failed**: one plain-language reason and one next action.

Do not stream chain-of-thought, provider polling or worker logs. Put them
behind a developer trace view.

## Security

- Treat prompts, uploaded files, reference URLs and provider responses as
  untrusted input.
- Never let user content change policy, price, budget or provider credentials.
- Keep provider keys server-side; never place them in model context.
- Use signed, expiring asset URLs; mirror outputs to your own storage.
- Moderate input and output; record the moderation policy version.
- Add rate limits, abuse detection and a manual kill switch.
- Consider watermarking and content provenance for generated media.

## Speculative work

Speculation is only safe when the marginal cost is small and the result can be
discarded silently. For media:

- Safe: prompt cleanup, safety pre-check, cost estimate, thumbnail, 480p draft.
- Unsafe by default: full 1080p/4K render, long video, paid third-party call
  with no cap.

Cap speculative spend per request and per day. Never let speculation create a
user-visible charge.

## Build order

1. Schema, idempotency keys and append-only ledger.
2. Quote and deterministic gate.
3. One provider adapter and one worker.
4. Outbox and crash-safe submit.
5. Polling plus webhook reconciliation.
6. Asset mirroring, checksum and validation.
7. Exception-only UI.
8. Watchdog and incident view.
9. Second provider and fallback policy.
10. Shadow routing, then percentage rollout.

## Tests that must exist

- Same idempotency key twice returns the same job and one charge.
- Crash after reserve but before submit releases or reconciles correctly.
- Provider timeout followed by success does not double-submit.
- Duplicate webhook creates one event and one settlement.
- Cancel racing with success produces one terminal state and one ledger result.
- Restart during `running` reconciles before any retry.
- Overbudget request is rejected before the provider call.
- Prompt injection cannot change price, budget or provider.
- Provider URL expiry is handled by the mirrored asset.
- Watchdog cannot exceed the mandate.

## Anti-patterns

- An LLM deciding price, credits or permission.
- Charging after submit, or charging from a webhook alone.
- Retrying an unknown provider state.
- Treating a provider URL as permanent storage.
- One mutable `status` string for the whole lifecycle.
- Streaming logs to the main product UI.
- Multi-agent role-play where a deterministic queue would do.
- Speculative full-resolution renders with no cap.
- Storing provider keys in prompts, job payloads or logs.
- Adding a second provider before the first one is reconcilable.

## Output checklist

- [ ] Every external effect has an outbox row and an idempotency key.
- [ ] Every job has an immutable mandate and a policy version.
- [ ] Every attempt has a provider job id, request hash and normalized result.
- [ ] Credits reserve, settle and release through the ledger only.
- [ ] Restart and duplicate-webhook tests pass.
- [ ] The UI has exactly four user-facing states.
- [ ] The watchdog can stop and downgrade, but not expand.
- [ ] Provider credentials never enter model context.

把约束落成一份 Harness 契约

harness.yaml 不是代码,而是一份可评审的契约:它把 planes、concepts、job states、gates、money path、provider adapter、recovery、watchdog、UI、security、SLO 写成机器可读的结构。你可以把它放进仓库,作为 PR 评审时的检查表;也可以让 coding agent 按它生成 schema、状态机和测试清单。

outputs/deepseek-super-harness/harness.yaml394 行
展开完整 harness.yaml(可复制)
version: 1
name: media-generation-harness
status: reference-contract
principle: "Spend as little user attention and money as possible per delivered asset."

planes:
  control:
    owns:
      - intake
      - quote
      - gate
      - authorization
      - scheduling
      - settlement
      - notification
    must_not:
      - run_gpu_work
      - trust_provider_text_as_state
  execution:
    owns:
      - provider_submit
      - provider_poll
      - download
      - validate
      - post_process
    must_not:
      - set_price
      - grant_permission
      - write_job_state_directly
  data:
    owns:
      - jobs
      - attempts
      - credit_ledger
      - assets
      - provider_events
      - policy_versions
    must_not:
      - hold_side_effects_only_in_memory

concepts:
  source:
    examples:
      - web_upload
      - public_api
      - stripe
      - moderation_callback
    rule: "Normalize the event and dedupe by source event id before it enters the domain."
  trigger:
    rule: "Immutable request or callback, stored before processing."
  thread:
    rule: "Project or conversation context. Never grants budget or permission."
  gate:
    rule: "Deterministic checks only. Runs before any model assessment."
  assessment:
    rule: "Semantic advice only. Cannot set price, credits, resolution or provider."
  decision:
    values:
      - accept
      - clarify
      - reject
      - defer
    rule: "clarify asks exactly one concrete question."
  mandate:
    fields:
      - policy_version
      - model
      - resolution
      - duration_seconds
      - max_cost
      - fallback_providers
      - rights
      - accepted_by
    rule: "Immutable. Any wider scope requires a new ticket and mandate."
  ticket:
    rule: "One billable generation job. Owns user-visible identity and ledger."
  assignment:
    rule: "One provider attempt. Retries create a new assignment under the same ticket."
  executor:
    rule: "Provider adapter or GPU worker with an isolated workspace and asset prefix."
  subscription:
    rule: "Context only. It never expands authority."
  channel:
    examples:
      - sse
      - websocket
      - webhook
      - email
    rule: "Ordered per ticket."
  outbox:
    rule: "Record the effect before performing it. Unique per ticket and effect key."
  policy:
    rule: "Catalog, price table and safety rules are versioned together."
  watchdog:
    rule: "External control loop. May stop or downgrade, never expand."

job:
  states:
    - draft
    - quoted
    - authorized
    - queued
    - submitted
    - running
    - downloaded
    - validated
    - ready
    - delivered
    - needs_input
    - blocked
    - failed
    - canceled
    - expired
  transitions:
    - from: draft
      to: quoted
      guard: "catalog entry exists and quote is complete"
    - from: quoted
      to: authorized
      guard: "gate passed and credits reserved"
    - from: authorized
      to: queued
      guard: "outbox row committed"
    - from: queued
      to: submitted
      guard: "provider accepted the request"
    - from: submitted
      to: running
      guard: "provider reports running"
    - from: running
      to: downloaded
      guard: "output mirrored to owned storage"
    - from: downloaded
      to: validated
      guard: "checksum, dimensions and moderation passed"
    - from: validated
      to: ready
      guard: "asset manifest committed"
    - from: ready
      to: delivered
      guard: "channel delivery recorded"
    - from: active
      to: needs_input
      guard: "one structured question is pending"
    - from: active
      to: blocked
      guard: "external dependency cannot proceed"
    - from: active
      to: failed
      guard: "normalized terminal error recorded"
    - from: active
      to: canceled
      guard: "cancel requested and provider state reconciled"
    - from: quoted
      to: expired
      guard: "quote TTL elapsed"
    - from: authorized
      to: expired
      guard: "authorization TTL elapsed before submit"
  terminal:
    - delivered
    - failed
    - canceled
    - expired
  rules:
    - "Only authorized may call a provider."
    - "submitted and running must carry provider_job_id and request_hash."
    - "Terminal states are immutable except for append-only audit events."

gates:
  order:
    - id: auth
      checks: ["session", "api_key", "ownership"]
    - id: shape
      checks: ["schema", "file_size", "url_allowlist"]
    - id: catalog
      checks: ["model", "resolution", "duration", "aspect_ratio"]
    - id: quota
      checks: ["credit_balance", "user_rate", "ip_rate"]
    - id: cost
      checks: ["max_cost", "daily_budget", "provider_price_version"]
    - id: pre_moderation
      checks: ["prompt", "reference_assets"]
    - id: availability
      checks: ["provider_health", "fallback_eligibility"]
  failure_contract:
    return:
      - stable_error_code
      - one_actionable_next_step
      - policy_version
    never:
      - raw_provider_dump
      - ambiguous_retry_instruction

assessment:
  enabled: optional
  inputs:
    - normalized_prompt
    - reference_asset_metadata
    - policy_version
  outputs:
    - accept
    - clarify
    - reject
  evidence_required: true
  cannot:
    - set_price
    - set_credits
    - raise_resolution
    - extend_duration
    - grant_provider_permission

money:
  ledger: append_only
  entries:
    - reserve
    - settle
    - release
    - refund
  flow:
    - "quote from policy version"
    - "reserve with ticket-derived idempotency key"
    - "commit job and outbox in one transaction"
    - "submit after commit"
    - "release on definite submit failure"
    - "settle actual on success"
    - "apply published refund rule on failure"
    - "reconcile provider usage daily"
  forbidden:
    - "charge from webhook without stored job and attempt"
    - "charge after submit with no reserve"
    - "ad_hoc_refund"

provider_adapter:
  methods:
    quote:
      input: ["normalized_request", "policy_version"]
      output: ["line_items", "currency", "expiry"]
    submit:
      input: ["normalized_request", "attempt_id", "idempotency_key", "callback_url"]
      output: ["provider_job_id", "accepted_at", "raw"]
    get:
      input: ["provider_job_id"]
      output: ["state", "progress", "output", "error", "raw"]
    cancel:
      input: ["provider_job_id"]
      output: ["accepted", "state"]
    parse_webhook:
      input: ["headers", "body"]
      output: ["provider_job_id", "event_id", "state", "raw"]
  normalized_states:
    - queued
    - running
    - succeeded
    - failed
    - canceled
    - unknown
  rules:
    - "Preserve raw provider payloads for audit."
    - "Never expose provider URLs as permanent assets."
    - "Never resubmit when provider state is unknown."

data_model:
  generation_requests:
    keys: ["client_idempotency_key", "owner_id"]
    fields: ["raw_input_hash", "created_at"]
  quotes:
    keys: ["request_id", "policy_version"]
    fields: ["line_items", "currency", "expires_at"]
  jobs:
    keys: ["ticket_id"]
    fields: ["mandate", "state", "current_attempt_id"]
  attempts:
    keys: ["attempt_id"]
    fields: ["provider", "provider_job_id", "request_hash", "response", "timings"]
  credit_ledger:
    keys: ["entry_id"]
    fields: ["ticket_id", "kind", "amount", "policy_version", "created_at"]
  outbox:
    keys: ["ticket_id", "effect_type", "effect_key"]
    fields: ["payload", "attempts", "sent_at", "last_error"]
  provider_events:
    keys: ["provider", "event_id"]
    fields: ["raw", "processed_at"]
  assets:
    keys: ["asset_id"]
    fields: ["storage_key", "checksum", "width", "height", "duration", "moderation"]
  policy_versions:
    keys: ["policy_version"]
    fields: ["catalog", "price_table", "safety_rules", "effective_at"]

recovery:
  startup:
    - "find jobs in submitted or running"
    - "query provider for each known provider_job_id"
    - "continue from provider result when present"
    - "fail attempt when provider has no record and submit deadline passed"
    - "never resubmit only because the local process restarted"
    - "settle or release only after reconciliation"
  invariants:
    - "Unknown provider state blocks retry."
    - "Duplicate webhook is a no-op after the first processed event."
    - "Cancel racing with success yields one terminal state and one ledger result."

watchdog:
  checks:
    - id: stale_job
      condition: "age > p99 for model"
      action: ["query_provider", "cancel_or_escalate"]
    - id: no_progress
      condition: "no provider state change for N minutes"
      action: ["query_provider", "retry_with_fallback"]
    - id: overbudget
      condition: "cost > mandate.max_cost"
      action: ["stop", "release_reserve", "alert"]
    - id: repeated_error
      condition: "same normalized error three times"
      action: ["open_incident", "stop_retrying"]
    - id: queue_slo
      condition: "queue wait > SLO"
      action: ["add_capacity", "degrade_catalog"]
    - id: webhook_storm
      condition: "duplicate event rate above threshold"
      action: ["dedupe", "alert"]
  write_set:
    - cancel
    - retry_within_existing_mandate
    - release_reserve
    - open_incident
  cannot:
    - raise_budget
    - raise_resolution
    - extend_duration
    - grant_permission

ui:
  user_states:
    working:
      shows: ["progress"]
      hides: ["logs", "chain_of_thought", "provider_polling"]
    needs_input:
      shows: ["one_question", "one_input"]
    ready:
      shows: ["preview", "download", "accept", "regenerate"]
    failed:
      shows: ["plain_reason", "one_next_action"]
  developer_trace:
    includes: ["provider_request_id", "normalized_error", "timings", "policy_version"]

security:
  untrusted:
    - prompt
    - uploaded_file
    - reference_url
    - provider_response
  rules:
    - "User content cannot change policy, price, budget or credentials."
    - "Provider keys stay server-side and never enter model context."
    - "Assets use signed expiring URLs and are mirrored to owned storage."
    - "Moderate input and output; record the moderation policy version."
    - "Rate limit, detect abuse and provide a manual kill switch."
    - "Consider watermarking and content provenance."

speculation:
  allowed:
    - prompt_cleanup
    - safety_precheck
    - cost_estimate
    - thumbnail
    - draft_480p
  forbidden_by_default:
    - full_1080p_render
    - full_4k_render
    - long_video
    - uncapped_third_party_call
  rules:
    - "Cap speculative spend per request and per day."
    - "Never create a user-visible charge for speculative work."
    - "Discard silently when the result is not useful."

slo:
  track:
    - quote_latency
    - queue_wait
    - submit_latency
    - provider_duration
    - success_rate
    - cost_per_delivered_asset
    - duplicate_charge_rate
    - reconciliation_lag
  targets:
    duplicate_charge_rate: 0
    ledger_mismatch_rate: 0
    reconciliation_lag_minutes: 15

来源

这一篇是基于第 1 篇的拆解做的场景翻译,不是 DeepSeek 原文;DeepSeek 讨论的是通用 Agent 系统,不是视频生成。原始链接与核验方式见 第 1 篇的来源与核验。

回到源头:设计拆解见 第 1 篇,媒体站 9 条启发见 第 2 篇。