这一篇是前两篇的产物:一份给 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 时,它会触发。
展开完整 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、状态机和测试清单。
展开完整 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 篇的来源与核验。