gooseworks 0.4.2 → 0.4.5-dev.15.facf60fb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +84 -22
  2. package/dist/agents/staging.d.ts +38 -0
  3. package/dist/agents/staging.d.ts.map +1 -0
  4. package/dist/agents/staging.js +309 -0
  5. package/dist/agents/staging.js.map +1 -0
  6. package/dist/auth/credentials.d.ts +1 -0
  7. package/dist/auth/credentials.d.ts.map +1 -1
  8. package/dist/auth/credentials.js +26 -8
  9. package/dist/auth/credentials.js.map +1 -1
  10. package/dist/auth/device-flow.d.ts +79 -0
  11. package/dist/auth/device-flow.d.ts.map +1 -0
  12. package/dist/auth/device-flow.js +411 -0
  13. package/dist/auth/device-flow.js.map +1 -0
  14. package/dist/auth/login-mode.d.ts +25 -0
  15. package/dist/auth/login-mode.d.ts.map +1 -0
  16. package/dist/auth/login-mode.js +87 -0
  17. package/dist/auth/login-mode.js.map +1 -0
  18. package/dist/auth/oauth-server.d.ts +2 -2
  19. package/dist/auth/oauth-server.d.ts.map +1 -1
  20. package/dist/auth/oauth-server.js +28 -5
  21. package/dist/auth/oauth-server.js.map +1 -1
  22. package/dist/auth/paste-login.d.ts +27 -0
  23. package/dist/auth/paste-login.d.ts.map +1 -0
  24. package/dist/auth/paste-login.js +203 -0
  25. package/dist/auth/paste-login.js.map +1 -0
  26. package/dist/commands/doctor.d.ts +29 -5
  27. package/dist/commands/doctor.d.ts.map +1 -1
  28. package/dist/commands/doctor.js +116 -63
  29. package/dist/commands/doctor.js.map +1 -1
  30. package/dist/commands/fetch.d.ts +1 -0
  31. package/dist/commands/fetch.d.ts.map +1 -1
  32. package/dist/commands/fetch.js +64 -29
  33. package/dist/commands/fetch.js.map +1 -1
  34. package/dist/commands/formats.js +2 -2
  35. package/dist/commands/formats.js.map +1 -1
  36. package/dist/commands/graphics/publish-flow.js +1 -1
  37. package/dist/commands/graphics/publish-flow.js.map +1 -1
  38. package/dist/commands/install.d.ts.map +1 -1
  39. package/dist/commands/install.js +126 -26
  40. package/dist/commands/install.js.map +1 -1
  41. package/dist/commands/launch.d.ts +3 -0
  42. package/dist/commands/launch.d.ts.map +1 -0
  43. package/dist/commands/launch.js +35 -0
  44. package/dist/commands/launch.js.map +1 -0
  45. package/dist/commands/login.d.ts +21 -1
  46. package/dist/commands/login.d.ts.map +1 -1
  47. package/dist/commands/login.js +112 -30
  48. package/dist/commands/login.js.map +1 -1
  49. package/dist/commands/logout.d.ts.map +1 -1
  50. package/dist/commands/logout.js +7 -1
  51. package/dist/commands/logout.js.map +1 -1
  52. package/dist/commands/skills.d.ts +50 -0
  53. package/dist/commands/skills.d.ts.map +1 -0
  54. package/dist/commands/skills.js +160 -0
  55. package/dist/commands/skills.js.map +1 -0
  56. package/dist/commands/styles.js +2 -2
  57. package/dist/commands/styles.js.map +1 -1
  58. package/dist/commands/tool.d.ts +17 -0
  59. package/dist/commands/tool.d.ts.map +1 -0
  60. package/dist/commands/tool.js +239 -0
  61. package/dist/commands/tool.js.map +1 -0
  62. package/dist/commands/update.d.ts.map +1 -1
  63. package/dist/commands/update.js +37 -6
  64. package/dist/commands/update.js.map +1 -1
  65. package/dist/commands/video-local.d.ts +9 -0
  66. package/dist/commands/video-local.d.ts.map +1 -0
  67. package/dist/commands/video-local.js +83 -0
  68. package/dist/commands/video-local.js.map +1 -0
  69. package/dist/commands/video-save.d.ts +3 -0
  70. package/dist/commands/video-save.d.ts.map +1 -0
  71. package/dist/commands/video-save.js +83 -0
  72. package/dist/commands/video-save.js.map +1 -0
  73. package/dist/config.d.ts.map +1 -1
  74. package/dist/config.js +14 -3
  75. package/dist/config.js.map +1 -1
  76. package/dist/environment.d.ts +9 -0
  77. package/dist/environment.d.ts.map +1 -0
  78. package/dist/environment.js +105 -0
  79. package/dist/environment.js.map +1 -0
  80. package/dist/index.js +12 -38
  81. package/dist/index.js.map +1 -1
  82. package/dist/lib/graphics-cache.d.ts.map +1 -1
  83. package/dist/lib/graphics-cache.js +2 -2
  84. package/dist/lib/graphics-cache.js.map +1 -1
  85. package/dist/lib/mcp-client.d.ts +53 -0
  86. package/dist/lib/mcp-client.d.ts.map +1 -0
  87. package/dist/lib/mcp-client.js +196 -0
  88. package/dist/lib/mcp-client.js.map +1 -0
  89. package/dist/lib/video-local-captions.d.ts +83 -0
  90. package/dist/lib/video-local-captions.d.ts.map +1 -0
  91. package/dist/lib/video-local-captions.js +261 -0
  92. package/dist/lib/video-local-captions.js.map +1 -0
  93. package/dist/lib/video-local-runtime.d.ts +8 -0
  94. package/dist/lib/video-local-runtime.d.ts.map +1 -0
  95. package/dist/lib/video-local-runtime.js +201 -0
  96. package/dist/lib/video-local-runtime.js.map +1 -0
  97. package/dist/lib/video-save-mcp.d.ts +9 -0
  98. package/dist/lib/video-save-mcp.d.ts.map +1 -0
  99. package/dist/lib/video-save-mcp.js +162 -0
  100. package/dist/lib/video-save-mcp.js.map +1 -0
  101. package/dist/lib/video-save.d.ts +77 -0
  102. package/dist/lib/video-save.d.ts.map +1 -0
  103. package/dist/lib/video-save.js +440 -0
  104. package/dist/lib/video-save.js.map +1 -0
  105. package/dist/program.d.ts +2 -0
  106. package/dist/program.d.ts.map +1 -0
  107. package/dist/program.js +56 -0
  108. package/dist/program.js.map +1 -0
  109. package/dist/skills/custom-video-skill.d.ts +3 -0
  110. package/dist/skills/custom-video-skill.d.ts.map +1 -0
  111. package/dist/skills/custom-video-skill.js +6 -0
  112. package/dist/skills/custom-video-skill.js.map +1 -0
  113. package/dist/skills/installer.d.ts +12 -6
  114. package/dist/skills/installer.d.ts.map +1 -1
  115. package/dist/skills/installer.js +82 -38
  116. package/dist/skills/installer.js.map +1 -1
  117. package/dist/skills/master-skill.d.ts +34 -28
  118. package/dist/skills/master-skill.d.ts.map +1 -1
  119. package/dist/skills/master-skill.js +2264 -858
  120. package/dist/skills/master-skill.js.map +1 -1
  121. package/dist/skills/package-freshness.d.ts +16 -0
  122. package/dist/skills/package-freshness.d.ts.map +1 -0
  123. package/dist/skills/package-freshness.js +36 -0
  124. package/dist/skills/package-freshness.js.map +1 -0
  125. package/dist/skills/releases.d.ts +12 -0
  126. package/dist/skills/releases.d.ts.map +1 -0
  127. package/dist/skills/releases.js +155 -0
  128. package/dist/skills/releases.js.map +1 -0
  129. package/dist/skills/routes.d.ts.map +1 -1
  130. package/dist/skills/routes.js +12 -6
  131. package/dist/skills/routes.js.map +1 -1
  132. package/dist/skills/staging-content.d.ts +3 -0
  133. package/dist/skills/staging-content.d.ts.map +1 -0
  134. package/dist/skills/staging-content.js +12 -0
  135. package/dist/skills/staging-content.js.map +1 -0
  136. package/dist/utils/browser-preflight.d.ts +29 -0
  137. package/dist/utils/browser-preflight.d.ts.map +1 -0
  138. package/dist/utils/browser-preflight.js +338 -0
  139. package/dist/utils/browser-preflight.js.map +1 -0
  140. package/dist/utils/http.d.ts +7 -1
  141. package/dist/utils/http.d.ts.map +1 -1
  142. package/dist/utils/http.js +5 -1
  143. package/dist/utils/http.js.map +1 -1
  144. package/package.json +11 -3
  145. package/skills/goose-ads/SKILL.md +509 -239
  146. package/skills/goose-product-photos/SKILL.md +161 -60
  147. package/skills/goose-video/SKILL.md +592 -259
  148. package/skills/goose-video-local/SKILL.md +1409 -236
  149. package/skills/gooseworks/SKILL.md +327 -34
  150. package/skills/make-custom-video/SKILL.md +735 -0
  151. package/skills/manifest.json +12 -0
  152. package/skills/routes.json +13 -6
  153. package/dist/skills/fetcher.d.ts +0 -18
  154. package/dist/skills/fetcher.d.ts.map +0 -1
  155. package/dist/skills/fetcher.js +0 -118
  156. package/dist/skills/fetcher.js.map +0 -1
@@ -2,307 +2,640 @@
2
2
  name: goose-video
3
3
  slug: goose-video
4
4
  description: >
5
- Order a finished video ad without leaving the chat. Use it when the user says "make me a video
6
- ad for <brand>", "I want a video ad", or asks for a UGC / iMessage / explainer video. One
7
- sentence is enough: it picks the brand, asks what the ad is for, suggests formats in a table
8
- with demo links, asks that format's questions, and shows the script for approval before any
9
- real spend. It renders on the GooseWorks server, returns the video in the chat, and works in
10
- hosted connectors too. For an existing app video project or batch, it hands off to
11
- goose-video-local.
5
+ Start a video ad in the same chat. Resolve the brand and suggest supported formats with a
6
+ picker or the returned text choices. An agent that can run shell commands on the customer's
7
+ computer makes the video with goose-video-local; otherwise the GooseWorks coworker makes it.
8
+ Review one complete template plan and total credits before production. Custom videos retain two authenticated review gates in this chat.
12
9
  category: ads
13
- version: 1.0.0
10
+ version: 3.1.0
14
11
  author: GooseWorks
15
- tags: [gooseworks, ads, video, order, server-render]
12
+ tags: [gooseworks, ads, video, local-render, coworker, chat]
16
13
  ---
17
14
 
18
- # GooseWorks Video Ads — order a video in chat
15
+ # GooseWorks Video Ads — choose, review, make
16
+
17
+ ## How to talk to the customer (applies to every message you send them)
18
+
19
+ The customer is a marketer or founder, not an engineer. Everything in this skill about tools,
20
+ fields, files, polling, models and pipeline steps is for YOU. Never pass it on to them.
21
+
22
+ - **Say what they get and what they need to decide, never how it's made.**
23
+ Bad: "Checking the media-proxy helpers for voiceover timestamps and lipsync." / "Generated with
24
+ gpt-image-2 at high quality, mouth closed, passes QC."
25
+ Good: "Recording the voices now." / "Here are your two hosts."
26
+ - **Never mention** tool, file or field names, ids, JSON, commands, scripts, installed software,
27
+ uploads, model or vendor names, timings in seconds or frames, retries, polling, internal statuses
28
+ or your own quality checks. Fix what you can quietly. If they ask how something works, explain it
29
+ simply.
30
+ - **Update them only at milestones they care about**: choices confirmed, script and voices done,
31
+ visuals done, ready to review, finished. One short line each, then stay quiet until the next
32
+ milestone. No update for setup, downloads, uploads or saving.
33
+ - **Money is in credits, with a number**: "the full video uses about N credits". Never "the cheap
34
+ pieces" or "the expensive render".
35
+ - **Problems:** say what it means for them and what they can do, in one or two sentences. No error
36
+ codes or stack traces. Only raise a problem that changes something for them.
37
+ - **Use their words**: script, voice, hook, scene, host, ending, logo, the product shot.
38
+ - **No filler**: no "Great question!", "Certainly!", "I hope this helps!", no restating what they
39
+ just said at length.
40
+
41
+ Example. Instead of a dozen lines about render tools, scripts, uploads and portraits, send:
42
+ "Got it: warm tone, home podcast studio, Brielle as the skeptic and Mark as the believer." then
43
+ "Script and voices are done (about 34 seconds)." then "Ready for you to review: the script, voices,
44
+ both hosts and the ending. You can also review your recipe ingredients in the app: <link>. Say go
45
+ here and I'll make the full video (about N credits)."
46
+
47
+ ## Use current instructions for new work
48
+
49
+ Before a new task in a terminal host, run `gooseworks skills status` once. It compares the
50
+ installed entry files, running CLI and published npm release. An older CLI needs a package
51
+ upgrade before `gooseworks update`; update alone only uses that CLI's bundled instructions.
52
+ Preserve local edits or unknown install provenance. Review/back up before explicitly replacing
53
+ modified files; never quietly reinstall over them. If the release check is unavailable, report
54
+ that freshness is unknown rather than claiming the local copy is latest.
55
+
56
+ For a new recipe run, fetch its package from the connected catalog. Retain the returned
57
+ `version` / `contentHash` and every dependency's hash with the saved package. When reusing a
58
+ saved fetch JSON, `gooseworks fetch <slug> --saved-package <file>` returns the current package
59
+ and a hash comparison without changing that file. With MCP, if the advertised `catalog_fetch`
60
+ schema accepts them, send `saved_content_hash` and `saved_dependency_hashes`; otherwise fetch
61
+ normally and compare the returned hashes yourself. Missing hashes mean unknown, not current.
62
+ The server cannot inspect a client's saved files; hash metadata does not certify later edits.
63
+ Fetch current packages into a new run directory and report stale saved instructions. Preserve
64
+ an existing approved run's recorded package; changing that harness requires a reviewed change
65
+ and approval before spending. Hosted installed snapshots use the existing Skills Update action.
66
+ Skill content and a host's cached MCP tool schemas are separate: refreshing one does not refresh
67
+ the other. Check the actual advertised tools before using new fields.
68
+
69
+ ## No GooseWorks tools in this session? Call them through the CLI
70
+
71
+ The GooseWorks tools named here (for example `account_whoami`, `brand_onboarding`,
72
+ `brand_read`) normally come from the GooseWorks connector. If they are not registered in this
73
+ session but you have a shell with the `gooseworks` CLI signed in (a cloud sandbox such as
74
+ ChatGPT agent, Meta AI or Grok), call the same tool from the shell:
75
+
76
+ ```bash
77
+ npx gooseworks tool <name> '<arguments as a JSON object>'
78
+ ```
79
+
80
+ - Run `npx gooseworks tool --list` once first. It prints the server's rules for using the tools
81
+ and every tool name; `npx gooseworks tool <name> --schema` shows one tool's arguments.
82
+ - The result prints as JSON. When a tool answers with a one-line summary, the fields you act on
83
+ (such as `next_step` and ids) follow under "Data:". It exits 1 when the tool reports an error.
84
+ - Nothing renders as a widget here, so tell the user the result in plain words.
85
+ - The same rules apply as over the connector: state the credit total and get the user's yes
86
+ before paid work.
87
+ - If it says you are not logged in, run `npx gooseworks login --device --no-wait`, show the
88
+ user the link and code it prints, and run `npx gooseworks login --device` once they approve.
89
+
90
+ When the tools are registered in this session, call them directly instead.
19
91
 
20
92
  ## Purpose
21
93
 
22
- Produces one finished vertical video ad, rendered on the GooseWorks server and billed in credits. Examples: an animated iMessage, ChatGPT or Notes thread that ends on the brand's product, or a kinetic-type explainer. The live catalogue decides what can be ordered.
23
-
24
- **The customer starts with one sentence.** "Make me a video ad for Bioma" is the normal opening, not an edge case. They will not name a format, a tool or a step. Getting from that sentence to a good choice is this skill's job.
25
-
26
- **The whole job happens in the chat.** The customer is in Claude Code, ChatGPT or a terminal. They will not open a browser to review a script, compare voices or watch a result. Choosing, previewing, approving and receiving all happen here, as text and links they can click. The app is for **payment and nothing else**.
27
-
28
- **This is not the Video Ads Lab.** The lab is internal, admin-only and free. This spends a customer's money.
29
-
30
-
31
- ## Route first: is this a new order?
32
-
33
- This skill **orders a new video**. Hand off to **`goose-video-local`** and stop following this skill when the customer brings any of these:
34
-
35
- - a **video batch** id ("for video batch <id>");
94
+ Resolve the brand, show supported formats and create one saved project. This is agent
95
+ execution, not a recipe server-order API.
96
+
97
+ **Who makes the video (one rule).** If you can run shell commands on the customer's own
98
+ computer, you make it there with `goose-video-local`: check the toolchain first and, if it
99
+ can't be fixed, hand it off. If you can't, hand it to the GooseWorks coworker with
100
+ `goose_run_task { brand_id, project_id, message: the customer's words }`; the one approval,
101
+ Stop and fixes stay in this chat. Inside the GooseWorks coworker's own sandbox you are the
102
+ coworker: make it yourself and never hand off. A hosted code sandbox is not the customer's
103
+ computer. Decide this yourself, never from the host's name; the server needs no flag for it.
104
+
105
+ **Cards and execution are separate.** A terminal may render without drawing a picker; a chat
106
+ app may draw a picker while the coworker renders. Follow `card.display_hint` for how to show
107
+ things; who makes the video is the rule above. Template-remix review stays in this
108
+ chat. Custom videos use separate authenticated script and ingredient approvals in this same chat.
109
+ Studio is an optional review surface.
110
+
111
+ ## Prepare the video workflow and brand before creative work
112
+
113
+ **Installed entry files are bootstrap instructions.** For new work started from an installed
114
+ file or an old chat, fetch `catalog_fetch { type: "skill", slug: "goose-video" }` on the selected
115
+ connection and read its returned content and dependencies before continuing. Use the already
116
+ fetched body when this entry came from that connection in this run; do not recursively fetch
117
+ the same entry. A CLI freshness warning does not block using the current connected package.
118
+ Keep the fetched package and hashes in a new run folder; do not replace edited installed files.
119
+ If current instructions cannot be loaded, resolve the connection before format suggestions,
120
+ script writing or production. Do not continue from an old installed workflow or session notes.
121
+
122
+ **Follow the connector's full-guide requirement when available.** Fetch
123
+ `catalog_fetch { type: "skill", slug: "gooseworks-guide" }` when the connector requires it.
124
+ The only older-server exception is below; it never removes brand preparation or approvals.
125
+
126
+ | Returned guide/workflow state on the selected connection | Required action |
127
+ | --- | --- |
128
+ | Guide returns `not_found`, and this same connection already returned the complete current matching video entry with its required dependencies | Continue with that authoritative entry's workflow, full brand preparation and existing approvals. |
129
+ | Guide has another error, no response, or incomplete content; or the matching entry/dependencies are missing, incomplete or from another connection | Stop before creative work, project writes or paid calls; resolve a compatible connection on this same environment. |
130
+
131
+ A missing custom entry or required custom tool/schema remains unavailable: never substitute
132
+ a template or an import to bypass it. Never hide other fetch errors or continue from cached
133
+ instructions merely because the guide was not found.
134
+
135
+ **For an existing project or batch, read `video_project_read` first.** Determine its actual
136
+ route and saved review state before fetching a recipe or doing local work. Resume an approved
137
+ run with its recorded packages, brand inputs, script and ingredients; do not silently replace
138
+ them with today's release or brand rules. Follow returned preparation requirements and use
139
+ the existing affected review/approval flow for an intentional change. Reads, free drafts,
140
+ imports and existing-job retrieval do not grant permission for new paid production.
141
+
142
+ **For every new plan, resolve the brand and call**
143
+ `brand_read { brand_id, sections: ["summary", "kit", "products", "learnings"] }` **before**
144
+ suggesting formats, mining video angles, writing any script, creating a production plan or
145
+ handing the work to another agent. Use `brand_get_context` with those same four sections only
146
+ when advertised. Reuse a complete read from this run on the same connection and brand.
147
+ Onboarding completion, a summary-only response and earlier session notes do not supply the
148
+ kit, selected product facts or saved rules. If the full read is unavailable, resolve it before
149
+ creative work. Use known kit facts while research is incomplete; do not restart research
150
+ solely because its status is pending. Never invent a product fact or accept a campaign to
151
+ bypass preparation. Then search the Brand Brain (next section) before suggesting formats,
152
+ choosing an angle or asking the user for a brand fact.
153
+
154
+ Carry that context through custom/template/idea routing and delegation. Before writing, make
155
+ the workflow's brand-rules file from those sources: pronunciations, required spoken copy,
156
+ prohibited claims, real logo, font, palette and the selected product's supported facts.
157
+ Entries prefixed `Video preference:` and visual/pacing instructions are creative direction,
158
+ not spoken lines. Use the kit's audience, offer and voice as defaults without an interview.
159
+ The rules file mirrors context; it is not proof of a read or permission to spend. Keep the
160
+ route's existing script, ingredient and budget approvals, including its allowed previews.
161
+
162
+ **Use the server's brand bundle when the selected API returns it.** Project create/read may
163
+ return top-level
164
+ `brand_context`: `{ version: 1, brand_id, digest, loaded_at, sections, brand, kit, products,
165
+ learnings }`. Its sections cover summary, kit, products and learnings.
166
+
167
+ | Returned brand/project response on the selected connection | Required action |
168
+ | --- | --- |
169
+ | A complete authoritative version-1 `brand_context` is returned | Binding is required: use its contents for the plan/rules and include `script_drafts.brand_context_digest = brand_context.digest` with each new/changed script save. |
170
+ | An older API returns the actual four brand sections but no `brand_context` in either the brand read or project response | Prepare from those full sections, write the brand rules and follow the selected connection's existing approval flow and advertised fields. Do not fabricate a receipt or send nonexistent bundle/digest fields. |
171
+
172
+ An incomplete/malformed bundle or missing brand section is a preparation failure, never the
173
+ older-API exception. Once this connection returns an authoritative bundle, use its binding
174
+ contract. `script_drafts.video_brand_context` is server-owned: do not author, replace or forge
175
+ it, and do not invent a read receipt or hash local brand facts into one. Keep the full bundle
176
+ out of client review payloads. If managed generation returns HTTP 409
177
+ `video_brand_context_required`, reload the project and bind the plan to its returned bundle
178
+ through the normal review flow before retrying. That refusal never permits the older-API
179
+ fallback, skipping sections or retrying paid calls blindly. Unchanged approved legacy resumes
180
+ retain their package, context and approvals; an upgrade alone needs no extra approval.
181
+
182
+ **To apply a deliberate brand correction to a new or revised plan**, make a fresh, unfiltered
183
+ `brand_read` of those four sections. When it returns the authoritative bundle, use its
184
+ returned `brand_context.digest` in the complete revised `script_drafts` through the existing
185
+ `video_project_upsert` `patch.script` save; the server verifies the current brand and replaces
186
+ its snapshot with the saved plan. On an older API with no bundle contract, apply the actual
187
+ four-section correction and save the revised draft through its existing flow without inventing
188
+ digest fields. If a connection that already returned a bundle cannot supply the fresh bundle,
189
+ resolve that failure before rebinding. Review and approve the affected script, ingredients and
190
+ budget through the normal flow. Never refresh an ongoing approved run automatically; its
191
+ recorded package and brand inputs stay pinned until an intentional change.
192
+
193
+ ## Search the Brand Brain, then propose — before any creative choice or question
194
+
195
+ The Brand Kit is a summary. The brand's saved knowledge (its Brain) holds what the Kit does not:
196
+ rules from past feedback, approved and rejected creatives, customer evidence, approved claims,
197
+ reports and documents. **After `brand_read`, and before you choose an angle, claim, hook,
198
+ product emphasis, format or source ad — and before you ask the user for any brand fact — call
199
+ `knowledge_search { brand_id, query }`** when it is registered. It is free and read-only: no
200
+ approval, no announcement to the customer and no questionnaire.
201
+
202
+ 1. **Search for this task, not the whole Brain.** Run one short query (under 500 characters) in
203
+ the user's own words plus the product (for example "ads for <product>: what worked, what to
204
+ avoid") and one with
205
+ `source_types: ["evidence", "claim", "learning", "creative", "document"]` for proof and past
206
+ creative results. Add a query only for a specific open question. Reuse results from this run.
207
+ 2. **Keep these states distinct** and record which one each query returned:
208
+
209
+ | Result | Means | Do |
210
+ | --- | --- | --- |
211
+ | `status: "ok"` with matches | Saved knowledge exists | Use it; keep each fact's citation in your working brief |
212
+ | `status: "empty"` | Nothing saved matches this query | Say "no saved evidence for <topic>", never "the brand has no proof" |
213
+ | `refresh_required` | The results shown are current; some changed sources were left out | Use them; search again shortly for anything missing |
214
+ | `building` | The index is not ready | Retry once shortly, then continue with the gap stated |
215
+ | An error, or the tool is not registered | Retrieval failed or is unavailable | Retry an error once, then continue from `brand_read` and treat evidence as unchecked |
216
+ | An empty Kit field | Only that field is blank | Not a search result: still search before asking |
217
+
218
+ 3. **Let the findings change the plan.** A `dont`/`must` learning or a rejected creative rules
219
+ options out; an approved or well-rated past creative is a proven angle to lead with; a report
220
+ shows what worked. Claim-grade proof is a result with `approved_ad_claim: true` or the kit's
221
+ `approvedClaims` within their stated applicability; the kit's `proofPoints` are what the
222
+ backend allows an ad to state, used as written. Other Kit text, documents and performance
223
+ numbers are context, never public claims. Never invent an offer, price or result. Judge
224
+ relevance and skip results about another product or business.
225
+ 4. **Propose; don't interview.** Lead with one recommended direction (angle, product, source or
226
+ format) and a one-line reason naming what you found, with up to two alternatives. When several
227
+ directions fit (two audiences, products or campaigns), pick the one the evidence favours, such
228
+ as an active campaign or approved past creatives, and name the other as an alternative instead
229
+ of asking. Ask only for a decision the brand read and the search cannot settle, or for spend
230
+ approval. Never ask the user for a fact the Brain already answered.
231
+ Keep directions already chosen (the user's words, a project's creative brief, handed-off
232
+ defaults, approved plans): search only to fill empty fields and to apply saved rules. When a
233
+ returned card or picker presents the choice, your recommendation is its one acknowledgement
234
+ line and the saved plan's defaults, not a separate list.
235
+ 5. **Carry an evidence brief** into the routed skill, writer or plan: findings with citations,
236
+ the state of each query, what it ruled out and the open gaps. Tell the customer the findings
237
+ in plain words; the citations stay in the brief.
238
+
239
+ On an approved resume, keep the saved brief and evidence; search again only for a new decision.
240
+
241
+ ## Custom videos: check formats first
242
+
243
+ First check the catalog: when the brief names or implies a listed format (for example a street interview, testimonial, podcast or chat video), show that format and its fit through the format flow below, including its support status. Go custom only when no format fits and the customer chooses custom after hearing why; custom keeps that format's hard constraints. For an original brief without a reference template, an Instagram reel/post URL or a direct video URL to study, fetch `catalog_fetch { type: "skill", slug: "make-custom-video" }` and follow it in this same session. It creates format:"custom", custom_mode:"generate" with the brief and optional reference_url. Growth executes in its managed sandbox; connected agents use their shell. Script and actual ingredients are reviewed and separately approved in the same chat before paid production; Studio is optional. Do not force a template that does not fit or that the customer declined, and do not import the reference as a finished video.
244
+
245
+ ## Run the format's route check before proposing it
246
+
247
+ A catalog row says what a format is best for and ranks it for the brand. It does not say whether
248
+ the format can make the setup the customer asked for (how many people, mic only, no product).
249
+ Some renderers declare a route selector that decides that; today that is the street interview
250
+ (`render-street-interview`). Before you propose, script or create a street-interview project,
251
+ template or custom, check the request against its routes:
252
+
253
+ | Street route | On screen | Needs | Makes |
254
+ | --- | --- | --- | --- |
255
+ | Guessing (`product-guess`) | Interviewer and up to four people | A physical product to hand over, photographed on its own | A finished video |
256
+ | Conversation (`mic-only`, `product-sample`, `concept-challenge`) | Interviewer and one person | No product photo, phone, screen or UI | A script and prompt preview only, no finished video yet |
257
+ | Street testimonial (its own format) | One person talking to camera, no interviewer | The creator still its recipe prescribes | A finished video, when the catalog lists it |
258
+
259
+ Neither interview route takes a photo of a person: people are described in text, so never
260
+ propose putting a founder's or creator's face in one. Keep the customer's named setup: never
261
+ change the interaction, mode or number of people just to make a check pass.
262
+
263
+ **Never offer guessing to software or a service.** The guessing route needs a physical product
264
+ to hand over. When what the brand sells is software, an app or a service (nothing a person can
265
+ hold), never offer or script guessing, not even with a laptop, phone or other device showing the
266
+ app as the prop: the device is a prop, so `offering_type` stays `digital` or `service` and the
267
+ selector says unsupported. A brand that sells a physical product, including a device or
268
+ hardware that comes with an app, can still use guessing with that product. Offer the table's
269
+ real alternatives instead: a one-person conversation (a script and prompt preview only, no
270
+ finished video yet), the street testimonial if the catalog lists it and does not rule it out,
271
+ or custom. Asking again never unlocks a route the table or the selector rules out.
272
+
273
+ When they asked for a street interview, the catalog may mark (newer servers do) the
274
+ street-interview rows `fit.ok: false` for such a brand: they can't make a finished video for it.
275
+ That verdict is about the finished video, so still offer the one-person conversation, saying
276
+ plainly that it is a script and prompt preview only. If the brand read shows the brand sells the
277
+ device itself but the catalog rules guessing out, tell the customer the catalog flagged it and
278
+ confirm what they sell before offering guessing with that device.
279
+
280
+ - **Without a shell on the customer's computer**, decide from this table. Do not fetch the renderer: its
281
+ package is far too large for a chat.
282
+ - **With a shell** (the customer's computer or the coworker sandbox), also run its free
283
+ selector, which makes no paid call. If the fetch or the run fails, decide from the table.
284
+ 1. Fetch the renderer to a file, not into the conversation: its inline package is over a
285
+ million characters. Either save the JSON from `gooseworks fetch render-street-interview > <file.json>`,
286
+ then write each `scripts` entry as
287
+ `/tmp/gooseworks-scripts/render-street-interview/scripts/<name>` and each `files` entry at
288
+ `/tmp/gooseworks-scripts/render-street-interview/<path>`. Or call
289
+ `catalog_fetch { type: "skill", slug: "render-street-interview", delivery: "archive" }`,
290
+ extract the ZIP, check each file against its `manifest.json` hashes and copy the contents of its
291
+ `agent-config/skills/render-street-interview/` folder into
292
+ `/tmp/gooseworks-scripts/render-street-interview/`. Either way the selector reads
293
+ `references/street-reference-library.json` from that folder.
294
+ 2. Write a brief JSON. `mode` and `interaction_type` go in pairs: `product-guess` with
295
+ `product-guess`, or `conversation` with `mic-only`, `product-sample` or `concept-challenge`.
296
+ `offering_type` is exactly `physical`, `service` or `digital` (software, SaaS and apps are
297
+ `digital`). `participants` is a whole number: people interviewed on screen, not counting
298
+ the interviewer. Leave it out when the customer named no count; the selector then uses the
299
+ route's usual cast. When the customer named no interaction, run each mode that could fit.
300
+ 3. Run `python3 /tmp/gooseworks-scripts/render-street-interview/scripts/prepare_script_context.py --brief <brief.json> --out <context.json>`.
301
+ Read `status` and `brief_gaps` in the output file, not the exit code: it exits 2 for every
302
+ status except `ready-for-writing`. A Python traceback is not a verdict: the brief or the
303
+ saved files are wrong (for example the reference library in the wrong folder). Fix them and
304
+ run it again.
305
+ 4. A run with any `brief_gaps` has not checked the route. Fix every gap and run it again.
306
+
307
+ **An unsupported setup is a stop.** `unsupported-route`, or a request the table rules out,
308
+ means this setup can't be made. Tell the customer plainly, in one line, for example: "A
309
+ three-person mic-only street interview isn't something we can make yet: that version takes one
310
+ person, and the three-person version needs a physical product to hand over." Then offer the
311
+ closest supported options in plain words, each with how it differs. With a shell, these are the
312
+ selector's `alternatives`. Without one, take them from the table:
313
+
314
+ - when only the number of people was the problem, the same setup within its limit: guessing
315
+ (a physical product only) with up to four people, a finished video; or a conversation with
316
+ one person, a script preview only;
317
+ - for something people can't hold (software, a service), a one-person conversation instead
318
+ of guessing;
319
+ - one person talking to camera with no interviewer, if the catalog lists that format;
320
+ - a custom video, untested, that keeps the street format's limits.
321
+
322
+ Never use route names or the script's wording. Do not create the project or write a script for
323
+ it. Go custom only if the customer picks it; a custom video keeps the street format's hard
324
+ constraints, which make-custom-video lists.
325
+
326
+ **A supported setup.** With empty `brief_gaps`, any other status means the route exists.
327
+ `needs-reference` means no observed reference ad matched yet: it does not block proposing the
328
+ format; carry it into the handoff so the script step resolves or reports it before script
329
+ approval. Carry the route too: which one, how many people, finished video or preview only, and
330
+ no person photos. A preview-only route makes a script and prompt preview, not a finished video:
331
+ say so before the customer chooses it.
332
+
333
+ ## Route first: is this a new video?
334
+
335
+ Hand off to **`goose-video-local`** now, and stop following this skill, for:
336
+
337
+ - an existing **project** id or a **video batch** id;
36
338
  - the app's copy-for-Claude command (it names `goose-video-local`);
37
339
  - "remix this video ad template" for a specific app template.
38
340
 
39
- Those render on the customer's own machine. Use `goose-video-local` if it is installed; otherwise load it with `fetch_skill("goose-video-local")` on the GooseWorks MCP.
341
+ Load the current `goose-video-local` entry with
342
+ `catalog_fetch { type: "skill", slug: "goose-video-local" }` on the GooseWorks MCP (older clients:
343
+ `fetch_skill("goose-video-local")`), unless already fetched on this connection in this run.
344
+ It reads the project first and retains an approved run's recorded recipe packages.
40
345
 
41
- A bare **project** id ("make the video for project <id>"): call `video_project_read { brand_id, project_id }` first. **Stay here** when it returns an `order`, or the project's `script_drafts.recipe` is set: that is an order made through this skill, so continue it on the same `project_id`. **Anything else** goes to `goose-video-local`.
346
+ When they ask **what** to make ("give me video ad ideas", "what angles should I use?", "what's
347
+ working for my competitors?"), fetch **`ad-angle-miner`** (`catalog_fetch { type: "skill", slug: "ad-angle-miner" }`,
348
+ using its advertised tools) and run it with the **video** output. It returns ranked
349
+ video ideas mapped to formats and hands the picked ones to `goose-video-local`.
42
350
 
43
351
  Everything else, including "make me a video ad for <brand>", starts at step 1 below.
44
352
 
45
- ## Inputs
46
-
47
- - A brand, usually named in the opening sentence. Resolved to `brand_id`; with one brand in the org it needs no input.
48
- - What the ad is for, in the customer's words (optional; asked once, never forced). **It is sent with the order** as `brief.prompt` and reaches the script. It is not only for sorting the table.
49
- - Anything else they volunteer about the ad: who it's for, names or terms it must say, things to stay away from. These are never asked for; they're kept when the customer offers them.
50
- - The picked format's own answers (voice, music, angle…). The catalogue says which apply; some formats ask nothing.
51
-
52
- Never an input: a reference video (these formats don't use one), or a promo code (it comes from the brand).
53
-
54
- ## Composed Atoms
55
-
56
- MCP tools; the work happens in the app.
57
-
58
- - `brand_list`: brand NAME → `brand_id`. Pass `query` when they named one.
59
- - `brand_create { name, website_url }`: only when the customer asks to add a brand that isn't there. Free.
60
- - `video_catalog_list { kind: "formats", brand_id }`: what can be ordered. Each format has `card.description`, `card.best_for`, `card.needs`, `ask[]`, `brand_requirements[]`, `default_credits` and `examples[]` (demo videos: a curated sample first, then real runs). The response also carries `brief_fields`: the intent fields every format's `brief` accepts on top of its `ask[]` (`prompt`, `audience`, `must_mention`, `avoid`, `notes`).
61
- - `video_catalog_list { kind: "voices" }`: voices with playable `preview_url`s, for formats that speak.
62
- - `video_project_upsert`: the free draft. Returns `project_id`, `quote`, `ready`, `missing` and `questions` (0–2 things worth asking before the script is written; usually none).
63
- - `video_project_upsert { project_id, patch: { brief } }`: changes the brief, checked the same way as when it was created. A key replaces its value, `null` removes it. Free. Once ordered, only the brief fields change (never the format's own answers), and they reach the script on the next `redraft`. A brief change never moves the quote.
64
- - `video_render_run { dry_run: true }`: re-reads the price. Free.
65
- - `video_render_run { kind: "partial" }`: **writes the script and stops.** Reserves the quote and shows the script before the expensive work. Works for every orderable format, chat or voiceover.
66
- - `video_render_run { kind: "full" }`: finishes an approved preview.
67
- - `video_render_run { kind: "edit", edit: { script } }`: puts the customer's OWN words into the draft, verbatim, through the format's own guards. Free before approval, and the **only** route that keeps their copy. Offered where the format's `edits.script` is non-empty; refused as `script_not_editable` elsewhere.
68
- - `video_render_run { kind: "redraft", reason }`: another draft, before approval, written from why they turned this one down, or with no `reason` after a brief change (the change is the reason). It **re-runs the writer** either way, so every word (and the picture on a character format) is replaced — it does not keep copy the customer supplied. Same order, **no new hold**, but **not free**: it adds a few credits of model spend inside the hold already placed. Up to 3, and refused if it would pass the hold. `dry_run: true` says what it would add.
69
- - `video_project_read { brand_id, project_id }`: status, the drafted script while previewing, and the finished video. Returns an `order` object for recipe projects.
70
- - `job_cancel { job_id }`: declines a preview. `job_id` is the order id (the project id also works). Releases the whole hold.
71
-
72
- ## Workflow
73
-
74
- The opening is fixed: **brand → what it's for → suggested formats → first question.** Do each step without waiting for the customer to ask for it.
75
-
76
- ### 1. Resolve the brand, quietly when you can
77
-
78
- Call `brand_list`, with `query` when they named a brand. `query` is a case-insensitive substring match, so "Kolkata Chai" also finds "Kolkata Chai Co". If it finds nothing, call `brand_list` once more with no query before deciding.
79
-
80
- - **The org has exactly one brand** → use it. Say which in one line ("Making this for **Bioma**.") and move on. Don't ask.
81
- - **The name they said matches exactly one brand** → use it. Say which.
82
- - **Several match, or they named none and the org has several** → show a table (name, website) and ask. The wrong brand is a wasted order.
83
- - **Nothing matches** → say so, list the brands they do have in a table, and offer to add the new one here: "Or send me its website and I'll add it." With a website, call `brand_create { name, website_url }` (free). It starts brand research, which fills in the logo and colours in a few minutes. Say so, then carry on from step 2 while it runs. Before step 5, check `brand_get_context` shows `research_status: complete`; until then drafts are blocked. Adding a brand is not a trip to the app. Never create a brand they didn't ask for, and never guess the website.
84
-
85
- If the GooseWorks MCP's own instructions have you check onboarding first and it turns out unfinished, finish it, then come back here with the customer's original sentence.
86
-
87
- ### 2. Ask what the ad is for, in one open question
88
-
89
- Unless the opening sentence already said it, ask **one** plain question and wait:
90
-
91
- > What's this ad for? For example: launching something, a sale, explaining how it works, or showing real results. Anything you tell me helps me pick the right format.
92
-
93
- This is a free-text question: **no menu, no table, no list of formats yet.** Take whatever they say, even "not sure" or "just something good". Never ask it twice, and never block on it: a vague answer is an answer.
94
-
95
- **Keep the answer. It goes into the order in step 5**, not just into the table's order. Write it down as they said it; that becomes `brief.prompt`. If their words (here or in the opening) also say who the ad is for, a name or term the ad must say, or something to stay away from, note those too:
96
-
97
- | They said | Goes in | Example |
98
- |---|---|---|
99
- | What the ad is for (the whole answer) | `prompt` | "creative is the bottleneck for small teams, make it a problem-solver" |
100
- | Who it's for | `audience` | "heads of growth at seed-stage startups" |
101
- | A name or term it must say | `must_mention` (a list) | `["Claude", "ChatGPT"]` |
102
- | Something to keep out | `avoid` (a list) | `["no villain", "don't talk down to marketers"]` |
103
-
104
- Never ask for the last three and never fill them with a guess. A `must_mention` term the customer didn't say is a claim we made for them.
105
-
106
- Skip the question when the opening already names a goal ("…a video ad for our summer sale") or a format ("…an iMessage video ad"). With a format named, go to the table with that format first and marked, then step 4.
107
-
108
- ### 3. Suggest formats in a table, best fit first
109
-
110
- `video_catalog_list { kind: "formats", brand_id }`, then **always a markdown table in your message**.
111
-
112
- Order the rows by how well each format fits their answer. Judge fit from `card.description`, `card.best_for` **and the format's `ask[]` options** against what they said. An option can make a format fit: a cartoon explainer whose `ask[]` offers `arc: hero-helper` fits "a friendly hero mascot, no villain"; one without that option does not, because its card says the character is the problem and loses. **A format whose card contradicts what they asked for is never Suggested**, however well its keywords match. When nothing fits, say so before the table ("None of our formats does X; the closest is Y, which gives up Z") and still show the table. Mark **exactly one** row **Suggested** with a few words on why ("real results → before/after"); a close second can be **Also good**. One suggestion keeps "the suggested one" unambiguous. With no goal given, put the formats that have demos first.
113
-
114
- | | Format | What it looks like | Price | Demo |
115
- |---|---|---|---|---|
116
- | **Suggested** | iMessage chat reveal | Two friends texting; ends on your product | ~60 credits | [watch](https://…) |
117
- | **Also good** | Apple Notes reveal | A diary-style note typed out; ends on your product | ~15 credits | [watch](https://…) |
118
- | | Kinetic type explainer | Bold on-brand text timed to a voiceover | ~80 credits | [watch](https://…) |
119
-
120
- - **Demo** is `examples[].output_url`. When a format has none, write "no demo yet" in the cell; never leave it blank.
121
- - **"What it looks like" is `card.description`, quoted.** Copy it word for word; you may cut it at a sentence boundary, never re-word it. A paraphrase once turned "narrates how it gets beaten" into "narrates the fix", which made a villain format look right for a no-villain brief. `card.best_for` often carries a dollar figure; never copy it into the table.
122
- - **When the pick depends on an option, say which.** "Cartoon explainer (as a friendly helper)" in the Format cell, and pre-fill that answer in step 4.
123
- - **Price** is `default_credits`, written approximately ("~60 credits"). It is a guide for choosing. The price they agree to is the server's quote in step 5.
124
- - **Say which formats won't take their words.** A format whose `edits.script` is empty writes its own copy and accepts no hand edit; the only lever is a redraft, which rewrites everything. Put "writes its own words" in that row's "What it looks like" cell. A customer who arrives with a script already written needs to know this **before** they pick, not after they hand it over.
125
- - **Formats the brand may not be able to run** go last, with `card.needs` in plain words, e.g. "needs real before/after photos". Don't hide them; don't suggest them. Judge logo and product photos from the `brand_list` row. For anything else (before/after photos, say) you can't see, treat it as missing rather than make extra calls. `video_project_upsert` returns `ready`/`missing`, which is the real check.
126
-
127
- **Print the table in your message, THEN ask which one** ("Want the suggested one, or another?"). Never put the formats only inside a structured question control: it renders plain option labels, not links, so the customer would be picking a format they were never able to watch. That happened on the first real run: the picker appeared, the demos didn't, and the customer had to ask where they were. A question control may follow the table to capture the answer; it never replaces it.
353
+ ## Keep the selected connection for the whole run
128
354
 
129
- ### 4. Ask the picked format's questions (only these)
355
+ Call `account_whoami` on the connection that owns the brand/project before the first write.
356
+ Keep `environment.name` and the public `environment.api_origin` with this run. Respect the
357
+ user's selected production or staging connection. If multiple connections are available and
358
+ none was selected, resolve that once. Missing or unknown identity is uncertainty, not
359
+ permission to switch. Never infer the environment from credits, billing links or Node mode.
130
360
 
131
- Its `ask[]` list is the whole question set. Anything with `source: "recipe"` is the recipe's call, never the customer's. **An empty `ask[]` means no questions**: say so ("This one needs nothing else from you") and go straight to step 5.
361
+ Reads, project creation, uploads, generation, polling and final updates all use that same
362
+ connection. If a production request fails, resume or report the failure on production;
363
+ never retry it on staging or recreate the project there. Before retrying a timed-out write,
364
+ read back the existing project or paid request on the selected connection.
132
365
 
133
- Ask them all in **one message**, the choice tables first. Put yes/no questions that have a default at the end as a statement they can override ("Music on and a selfie in the thread; say if you want either off"). Every question with an optional answer gets a "leave it to the writer" option.
366
+ Local/CLI proxy origin must match `environment.api_origin`. If it differs, use the MCP
367
+ relay on the selected connection (`GW_MEDIA_VIA=mcp`) before paid calls. Do not change
368
+ credentials or API origins to recover a failed write. An explicit user-requested move is
369
+ a separate operation, with the existing project and paid requests reconciled first.
134
370
 
135
- **The rule most easily got wrong:** anything the customer should *see or hear before choosing* goes in a **markdown table in your message**. The structured question control only captures the answer afterwards; it cannot render a link, so a demo or a voice sample placed only in the widget is a choice nobody can actually evaluate. Table first, question second. Yes/no questions need no table, and a choice with no sample (an angle list) is still a table, just without that column.
371
+ ## Check assets for the selected format before spending
136
372
 
137
- | Angle | The thread | Demo |
138
- |---|---|---|
139
- | friend-asks-friend | A friend notices something and asks | [watch](…) |
140
- | setup-flex | You send a photo, the friend reacts | [watch](…) |
141
- | swap-moment | You quit something worse for this | [watch](…) |
142
- | feature-as-punchline | The product's own mechanic is the reveal | [watch](…) |
373
+ For a template, read its structured `asset_readiness` from
374
+ `video_catalog_list { kind: "formats", brand_id }`. `missing` names gaps;
375
+ `needs_review` means suitability is unverified, including older recipes without
376
+ structured requirements. `ready` describes assets only, not script or budget approval.
377
+ For a custom video, derive requirements from its actual approved scenes.
143
378
 
144
- Same for voices: `video_catalog_list { kind: "voices" }` gives name, gender, accent and a `preview_url`. Put the name in a link so it plays. This was the clearest moment of the first real run: a voice table with playable samples. Do the same for any avatar or style choice a format exposes.
379
+ Inspect candidate files for the chosen product and format. A catalog photo can contain
380
+ multiple objects, other products or a person; its existence or approved status does not
381
+ make it a standalone image of the selected product. Check object count, framing, readable
382
+ print and real image bytes. A service conversation has no automatic packshot requirement.
145
383
 
146
- A choice with an `enum` and no samples (the cartoon explainer's `style`, say) is still a table: one row per value, described in the words of the field's own `description`, with "no demo yet" in the sample column. Don't invent what a style looks like.
384
+ Reuse a suitable approved image first. A free crop or cutout is a new file: keep the
385
+ original, inspect the result and include it in the normal ingredient review. When no
386
+ usable input exists, explain the gap before generation. Estimate any paid preparation
387
+ separately before spending. Record the selected asset and inspection in the project
388
+ review set; do not add a separate approval round.
147
389
 
148
- Never ask for a promo code.
149
-
150
- ### 5. Draft and price
151
-
152
- `video_project_upsert { brand_id, name, format, brief: { …answers, prompt, audience?, must_mention?, avoid? } }` is free. `brief` holds the picked format's `ask[]` answers **plus** the step-2 intent. Leave out any intent field the customer never gave. If `ready` is false, **stop** and relay `missing` in plain words.
153
-
154
- The server refuses any other key with `invalid_brief` and names every key it accepts. Fix the brief from that message; never drop the customer's intent to make the error go away.
155
-
156
- **If `questions` is not empty, ask them now, in one message, before showing the price.** Each one says which brief field its answer fills (`fills`). Save the answers with `video_project_upsert { project_id, patch: { brief: { <fills>: <their answer> } } }`. The bounds:
157
- - **One round.** Never ask a follow-up, and never ask the same thing twice.
158
- - **Never required.** If they skip them or say "just make it", carry on with the draft as it is.
159
- - **Empty `questions` means ask nothing.** The server skips it when the brief already says what the ad is for and who it's for, and never asks what the brand record already knows. "Make me a video ad for X" stays a complete request.
160
-
161
- Show the price the draft returned (the `quote`), **never a number from this page or the catalogue**. If it differs from the table's "~N", the quote is right. Convert at **100 credits = $1**.
162
-
163
- ### 6. Write the script and show it, before the expensive work
164
-
165
- `video_render_run { brand_id, project_id, kind: "partial" }`.
166
-
167
- This reserves the credits and writes the script only: **no video is rendered.** It returns immediately with `status: "previewing"` and no script yet; the writing happens in the background. Poll `video_project_read` every 20 seconds (it lands in about 40) and read `order.preview`. Calling `kind: "full"` while it is still `previewing` is refused with `preview_in_progress`.
168
-
169
- `order.preview` is shaped by the format, and **every** orderable format has one. A preview was chat-only until 2026-09-25, and a kinetic-type customer approved a price and first saw the copy in the finished video.
170
-
171
- **A chat format** (iMessage / ChatGPT / Notes) fills `thread` (the message list), `angle`, `cta_text` and `selfie_url`. Show it as a readable transcript, not JSON:
172
-
173
- > **them:** wait why do you look so into your phone rn 😭
174
- > **me:** I literally just chose to betray the duke
175
- > **them:** omg is this a game or a book
176
-
177
- **`selfie_url` is a generated face — show it as a link.** These formats draw the selfie INSIDE the script step, so it never appears in `anchor_images` and `stage` stays `"script"`. It is still a face that will be in the ad, so it still needs their yes.
178
-
179
- **Any preview with `order.preview.stage: "image"`** stopped after the **picture**, not just the words, so they judge the face before paying for the video. Go by that field, never by a list of format names — it follows what the recipe's steps produce, so a format joins this branch without a skill edit. Today: the animated character, the reaction selfie, the podcast hosts, the UGC creator, and the voiceless dance story (four to six stills, and no spoken script at all — its stills and `detail` ARE the draft, so say that rather than reporting an empty script).
180
-
181
- Show every URL in `order.preview.anchor_images` as a link (the podcast format has two, one per host), alongside the script. Then say plainly, in this order: what they are looking at; that **nothing has been rendered yet and the pause is deliberate**, because the video is generated FROM this picture and changing the face now is cheap; that **approving starts the render and commits the credits already held**; and that cancelling instead releases the whole hold, so the picture costs them nothing. A pause with no reason given reads as a broken order.
182
-
183
- **A voiceover format** (kinetic type) fills `beats` instead, and `thread` is null. Each beat has `vo_lines` (what is spoken) and a `beat` label. `detail.hyperframe.plan.slates[].props` holds the words that go **on screen**, which for this format is most of the ad. Show both columns, in order:
184
-
185
- | # | On screen | Voiceover |
186
- |---|---|---|
187
- | 1 | Sunscreen that pills | Most sunscreens pill under makeup. |
188
- | 2 | Myth: all SPF is greasy → Fact: not a fluid one | This one is a fluid. It sinks in. |
189
-
190
- Read the spoken lines out as one paragraph underneath, so they can hear the pacing.
191
-
192
- Whatever the format, **quote it back verbatim.** Do not paraphrase, tidy or shorten the copy; they are approving the exact words that will be rendered.
193
-
194
- If `order.preview.brief_check` is present, read it before asking for approval:
195
- - `missing_mentions`: terms they asked for that the script never says. Tell them which, plainly ("It doesn't mention ChatGPT yet").
196
- - `endorsement_flags`: lines that make another brand sound like it endorses, partners with or ranks theirs. Show the line and say it has to change: a listed name may appear as "works with", never as "recommends" or "#1 for".
197
-
198
- If `order.brief_changed_since_draft` is present, the brief was changed after this draft was written. **Approving renders the draft as shown**, so redraft first (no reason needed), or tell them the change won't be in this video.
199
-
200
- Then ask: **use this, change it, or stop?**
201
-
202
- **One question decides how to change it: did they give you the actual WORDS, or did they tell you what is wrong?** Only the first route below keeps their words. The other two write new ones — which is right when the customer wants something different, and is a silent rewrite when they wanted what they wrote.
203
-
204
- - **They gave you the words** ("use these lines", "the end card must say Meet Goose", "keep this but change the second bubble") → `video_render_run { kind: "edit", edit: { script: { … } } }`. **This is the only route that keeps copy verbatim.** It puts their text through the format's own guards and costs nothing extra before approval. Send the shape the preview handed you: `script.thread` for a chat format, `script.beats[i].vo_lines` (one entry per beat, `{}` for a beat you are not changing) for a voiceover format, `script.slates[{ beat_idx, props }]` for on-screen words, `script.cta_text` for the end card, `ingredient: "character"` + `script.character.description` for the character itself.
205
- - **An edit before approval IS the approval**: it applies the copy and starts the render at once (status → `running`). So show the exact words you are about to send, get a yes, and only then send it. An unapproved fix stays a proposal in the chat.
206
- - Refused with `script_not_editable` → this format writes its own copy and takes no hand edit. **Say so, and do not paraphrase their lines into a redraft** — that hands them a video that is not what they wrote. Their real options are a redraft (new words, not theirs), a re-order with different answers, or cancel.
207
- - Refused with `script_rejected` → a line makes a claim the brand's facts don't support. Relay the reason and offer a rewrite; never argue it through.
208
- - **They changed the BRIEF, not the copy** — the problem it shows, who it's for, a name to say, something to avoid, a note like "keep it dry" → save it with `video_project_upsert { project_id, patch: { brief } }` (use `notes` for anything that isn't one of the other fields; keep their original `prompt`), then `video_render_run { kind: "redraft" }` with no `reason`. The new draft is written from the updated brief, so **the words will be new** — this changes the instructions, not the script. Same cost wording as below: no new order, no new hold, a few credits inside the hold.
209
- - **They only said what's wrong** ("too salesy", "the character looks like a mug") → ask **why** in one line, then `video_render_run { kind: "redraft", reason: <their words> }`. **A redraft RE-RUNS the writer: every word, and the picture on a character format, is replaced.** Their reason steers the next draft; it is not copied into it. Never put exact lines in `reason` expecting them back. Say what it costs, precisely: "no new order and no new hold; it adds about N credits to what this video costs, inside the hold" (N from the response's `redraft.credits_estimate`). **Never call it free.** Poll `video_project_read` until `preview` again and show the new draft plus `order.spend` (what each draft added, how much of the hold is left). A `redraft_exceeds_hold` or `redraft_limit` refusal means: approve a draft or cancel.
210
- - Different answers altogether (another format, another angle) → a fresh project, and cancel this one.
211
- - Stop → `job_cancel { job_id: <order id> }`. The whole hold is released and they pay nothing. Cancel only works while the order is `queued`, `previewing` or `preview`; once they approve and it is `running`, it is being made and cannot be refunded.
212
-
213
- This is the review. It happens here, not in the app.
214
-
215
- **If the server answers `preview_not_available`,** that is a bug: every sold format has a script preview. Say plainly "I can't show you the script for this one before it's made", and stop. Do not quietly skip the step and charge as if the gate had passed.
216
-
217
- ### 7. Finish it
218
-
219
- Only after they approve the script: `video_render_run { brand_id, project_id, kind: "full" }`.
390
+ ## Inputs
220
391
 
221
- Poll `video_project_read { brand_id, project_id }` **every 30 seconds.** Done means `order.status` is `done`; the same response then carries `video_url` (the project page) and `mp4_url` (the file).
392
+ - A brand, usually named in the request. Resolve it from the accessible brands; one brand needs no question.
393
+ - The customer's goal, occasion, audience, selected angle and constraints when supplied; otherwise propose defaults from the verified kit.
394
+ - The selected format's required assets. Presence does not prove suitability; uncertain assets remain “needs review.”
395
+
396
+ ## Save durable brand answers, then verify them
397
+
398
+ Read the selected brand with `brand_read { brand_id, sections: ["summary", "kit", "products", "learnings", "onboarding"] }`
399
+ (older clients: `brand_get_context` with the same sections, only when that tool is advertised).
400
+ Keep founder answers, user corrections, research and your own hypotheses distinct. Reuse
401
+ matching saved answers; ask only about gaps.
402
+
403
+ When the user asks to remember a rule, answers a brand interview, or explicitly corrects a
404
+ standing fact, save that answer in the same turn. The capture request authorizes those answers;
405
+ do not ask for approval again. A direction for this one video stays in its brief. If the scope
406
+ is genuinely ambiguous, ask whether it applies to future videos before saving a standing rule.
407
+
408
+ Use the **live registered schema**. Where supported, call `brand_update` with
409
+ `knowledge_intent: "user_correction"` and `user_statement` containing the user's exact,
410
+ verbatim answer, not your paraphrase or researched text.
411
+ For an inference or suggested improvement, use `knowledge_intent: "agent_proposal"`. Show the
412
+ before/after change from your prior read and proposed value; retain the returned proposal IDs
413
+ and say the user must accept it in the app. Link only a review surface actually returned by a
414
+ tool; the compact `knowledge_updates` response does not itself contain a diff or URL.
415
+ A pending proposal is not a saved fact. Never call an unavailable
416
+ tool or silently relabel research or your inference as something the user said.
417
+
418
+ The safe structured shape is `patch: { knowledge: { positioning?, audience?, voice?,
419
+ instructions?, brandType?, tagline?, valueProps? } }`, using only fields present in the live
420
+ schema. Inferred rules/taste go in an `instructions` proposal with a rationale, never in
421
+ `patch.facts`. Prefix every video-only preference in that proposed text with "Video preference:"
422
+ so it remains production direction after acceptance. Preserve unrelated instructions when
423
+ proposing a merged replacement.
424
+
425
+ | User answer | Canonical write |
426
+ | --- | --- |
427
+ | Primary audience, positioning or voice correction | `patch.knowledge: { audience/positioning/voice: <answer> }` (one actual key). During onboarding, `brand_onboarding { action: "review_research", review: { action: "correct", field, value } }` writes these existing corrections with provenance. |
428
+ | Founder story, customer pains, objections, buying trigger or useful audience detail without a structured field | `patch.facts: [{ kind: "insight", text }]`; retain attribution such as "Founder reports: …" rather than turn a belief into a verified result. |
429
+ | Required wording or pronunciation | `patch.facts: [{ kind: "must", text }]`; pronunciation is exactly `Pronounce "<term>" as "<say_as>"`. |
430
+ | Forbidden claim, word or visual | `patch.facts: [{ kind: "dont", text }]`. |
431
+ | Durable visual, voice or pacing preference | `patch.facts: [{ kind: "do", text }]` for a preference; `dont` for an avoidance; `template_hint` for a preferred format. Prefix video-only preferences with "Video preference:". |
432
+
433
+ Facts are existing `ad_brand_learning` rows with user provenance; they are not a second profile.
434
+ Update a matching rule by its returned `id` instead of adding duplicates. Preserve unrelated
435
+ rules and the user's exact meaning. Only use the legacy facts shape for explicitly user-authored
436
+ answers when the live schema lacks intent fields; agent suggestions still need a proposal path.
437
+
438
+ **Claims and plans have separate gates.** A founder assertion or proof point is not an approved,
439
+ evidence-backed claim or consent to quote a customer. Use the existing evidence/claims and
440
+ operating-plan tools only if registered, following their proposal, evidence and confirmation
441
+ requirements. Never encode a spend cap, approver or emergency stop as a learning. If that write
442
+ path is missing, report the specific unsaved item and keep it pending for the supported review
443
+ surface; do not claim it was saved or create a parallel local profile.
444
+
445
+ After every write, **read back before saying saved**: use `brand_read` with `kit`, `learnings`
446
+ or `onboarding` as appropriate, or `brand_onboarding { action: "status", brand_id }` after an
447
+ onboarding answer. Verify the intended field/rule, its source and the absence of a conflicting
448
+ duplicate. A generic success response, pending proposal, ignored key or truncated result is not
449
+ proof. Report partial saves honestly. Carry the verified rules into the current task and the
450
+ routed skill; claims still pass their own safety gate.
451
+
452
+ ## Video taste — reuse examples and preferences
453
+
454
+ When asked to capture video taste, or when the user volunteers a durable video preference,
455
+ first read the saved learnings and `media_list { brand_id, scope: "brand", scope_id: brand_id,
456
+ tags: ["video-taste"], limit: 100 }`. Follow `next_cursor` before deciding an example is absent.
457
+ Do not force a taste interview before an unrelated task or ask again for an existing preference.
458
+
459
+ Save what the user has already supplied first. Then ask only the missing useful question, for
460
+ example: "What do you like about this video—its pace, voice, captions, or look?" An inaccessible
461
+ link can still be saved as a link with the user's explanation; do not pretend you watched it.
462
+
463
+ - **Direct clip or video file:** register with `media_upload { brand_id, scope: "brand",
464
+ scope_id: brand_id, kind: "video", source: { type: "url", url }, tags: ["video-taste", "reference-only"],
465
+ metadata: { purpose: "video_taste", source_url: url, provenance: "user", captured_at: <ISO timestamp>,
466
+ preference: <the user's explanation> } }`. For a file use the live file/bytes upload flow and
467
+ `media_confirm` after a presigned upload. Registration of a URL does not copy or inspect it.
468
+ - **Instagram/post/page link:** the same registration with `kind: "document"`; it is a link
469
+ bookmark, not downloadable footage or an indexed transcript. Do not fabricate a direct clip URL.
470
+ - **Preferences:** save the user's reasons, likes and dislikes through the facts mapping above.
471
+ Read and preserve existing facts before updating one. Do not invent `video_preferences` or
472
+ new `video_lab` keys; the live kit patch accepts only its documented asset fields.
473
+ - **Deduplicate:** reuse a matching returned media row, then `media_update` its title/tags/metadata
474
+ if needed; preserve existing metadata and tags. Do not create another row for the same example.
475
+ Read back with `media_list` and `brand_read` learnings before claiming it was saved.
476
+ Check the returned row belongs to this brand: URL deduplication may return another brand's
477
+ existing row. Do not relabel that row or claim success unless the current brand's scoped read
478
+ actually returns it. Report an unsaved association if no supported attach path is available.
479
+
480
+ Never put third-party taste examples into kit reference images: `kind: "reference"` at brand
481
+ scope writes there. The tags and metadata above record purpose and provenance; **they do not
482
+ grant or enforce usage rights**. Study the structure, pacing and look only. Never use the example's
483
+ footage, face, product, testimonial or claims in a new ad without independently verified permission.
484
+
485
+ Read video-only entries prefixed `Video preference:` from both saved learnings and
486
+ `kit.instructions`, including accepted proposals. Keep them out of required or forbidden
487
+ dialogue. Build a brief from the verified readback: preferred pace, voice, caption treatment, visual style,
488
+ formats to favour/avoid, reference links and the user's reasons. Say what is still unknown.
489
+ Pass it with the brand rules into the existing video workflow. A one-video request overrides a
490
+ default for that project; it does not silently rewrite the brand's standing preference.
222
491
 
223
- **How long depends on the format.** One number is wrong by 2x across the catalogue, and a customer told "about 5 minutes" at minute 9 thinks it has failed:
492
+ ## Composed Atoms
224
493
 
225
- | Format | Typical | Give up after | Why |
226
- |---|---|---|---|
227
- | iMessage / ChatGPT / Notes chat reveal | ~5 min | 10 min | No video is generated; a browser renders the phone UI, then ffmpeg cuts it |
228
- | Kinetic type explainer | **~10 min** | 20 min | Two AI b-roll clips, a voiceover, a music bed, HTML slides and a stitch |
494
+ Use the live canonical schemas: brand_read, brand_create, video_catalog_list,
495
+ video_project_upsert, video_project_read, catalog_fetch, goose_run_task and job_cancel.
496
+ An old tool name in a recipe is not grounds for terminal-update advice in a chat host.
229
497
 
230
- An unlisted or new format: assume the longer budget. Say the number you are working to up front ("this takes about ten minutes; I'll keep checking"), and say something at the halfway mark rather than going silent. Waiting is not failing; silence feels like it.
498
+ ## Paid media: images, clips, voice
231
499
 
232
- ### 8. Deliver in the chat
500
+ **No FAL_KEY, ElevenLabs key or `fal_client` is ever needed.** Use
501
+ `data_post_provider { provider: "fal", path: <model id, e.g. "fal-ai/nano-banana/edit">, body: <model input>, project_id }`
502
+ then `job_get { job_id }`; voice/music use provider:"elevenlabs". Upload local inputs through
503
+ media_upload first. **A missing key is never a blocker.** `photos_generate` is **not** a general
504
+ image tool: it photographs physical catalog products only. Use goose-video-local for the
505
+ runtime, paid approval, asset saving and quality checks.
233
506
 
234
- **Lead with `video_url`** — the project page, where the video plays and they can come back to it. Give `mp4_url` second and name it as the file ("and the raw MP4, if you want to download or upload it"). Never hand over the CloudFront `.mp4` on its own: it is a file, not a place — nothing to return to, nothing to edit from, and it reads like a debug artifact rather than a delivery. If `video_url` is missing, poll once more; never substitute the mp4 for it silently.
507
+ ## Workflow
235
508
 
236
- Say what the video is: length, ratio, what's in it. This is delivery **in the chat** — the link is the video, not an instruction to go to the app. If they want a change, offer to make another with different answers. That is a new order and a new charge; say so.
509
+ ### 1. Resolve the brand, quietly when you can
237
510
 
238
- The only reason to send someone to the app is **payment**: not enough credits, or a plan without video.
511
+ Call brand_read without brand_id to list accessible brands. Resolve the customer's name from
512
+ actual returned names. One brand, or exactly one match: state it and continue. Several possible
513
+ brands: show names and websites and ask once. No match: show accessible brands and offer to add
514
+ one only when the customer asks and supplies its website; never guess the URL.
515
+
516
+ Read `brand_read { brand_id, sections: ["summary","kit","products","learnings"] }` before
517
+ choosing a format. Reconcile explicit brand corrections through the durable capture rules.
518
+ A direct video request does not require first-campaign acceptance. Follow any returned
519
+ request-specific setup requirement; never mark research complete or accept a campaign to
520
+ bypass setup. Keep the original request.
521
+
522
+ ### 2. Keep the goal and propose defaults
523
+
524
+ Never ask what the ad is for before showing formats. Keep the customer's exact direction.
525
+ Otherwise propose defaults from the verified kit's products, audience, offer and voice and the
526
+ Brain search's evidence brief (proven angles, saved rules, approved claims). Show those defaults
527
+ in the saved plan so any can be changed. Never invent proof or product facts.
528
+ Idea requests still follow ad-angle-miner with the video output.
529
+
530
+ ### 3. Show the picker or its text fallback
531
+
532
+ Call `video_catalog_list { kind: "formats", brand_id }`. Every caller gets every format.
533
+ Respect requested limit and next_cursor for more item pages; the card may
534
+ contain a full picker independently of the item page.
535
+
536
+ - **card.display_hint:"widget"**: the picker replaces a format table. Write at most one short
537
+ acknowledgement, print no table or list of the formats, and wait for the selection.
538
+ - **Otherwise**: print card.text_summary as returned. Without a card, show returned rows with
539
+ names, faithful descriptions, needs and demo links, respecting the requested page size.
540
+ Write "no demo yet" when absent. Never put links only in a question control.
541
+ - A format whose card contradicts what they asked for is never Suggested. A `needs_browser`
542
+ format is made only on the customer's computer (the coworker has no browser): if you have no
543
+ shell there and the customer picks one, say it needs GooseWorks running on their computer and
544
+ offer another.
545
+ - A row with `fit.ok: false` is ruled out for this brand. Never propose it unless the customer,
546
+ after hearing why, still asks for it. For a street interview, insisting never unlocks a route
547
+ the route check rules out; when they asked for one, the one-person conversation preview is the
548
+ one exception (see the route check). When they ask about a ruled-out row, tell them why in
549
+ plain words (its `fit.reason`), never the field name.
550
+ - `suggested` ranks a row for the brand; it never means the row makes the setup they named.
551
+ Never tell the customer a row is "exactly" what they asked for beyond what its card says. A
552
+ street interview still needs the route check above first.
553
+ - Missing required assets are missing; unknown suitability is “needs review.” Inspect the
554
+ selected format's candidates before spending. Never promise an unverified asset is ready.
555
+ - Template formats use **one plan, one approval with the total in credits**, not approval for
556
+ each paid step. Custom videos retain separate authenticated script/ingredient/budget gates in this chat.
557
+
558
+ ### 4. Check this machine can render it
559
+
560
+ - **No shell on the customer's computer:** skip local checks and hand the project off in Step 5.
561
+ Never tell the customer they need Claude Code to start. Give computer setup guidance only if
562
+ asked how to make a `needs_browser` format.
563
+ - **Local shell:** run `gooseworks doctor --no-browser` for common setup (auth/MCP, Node 18+,
564
+ ffmpeg with libx264 (libass only for an ASS caption route), ffprobe). Then fetch the selected template and its capabilities,
565
+ and install documented dependencies in its fetched folder. Do not guess a renderer from a
566
+ format name. For each Node renderer using default Playwright Chromium, run
567
+ `gooseworks doctor --renderer-script "/absolute/path/to/the/fetched/scripts/record.js"` with
568
+ the exact environment used to render, including NODE_PATH and PLAYWRIGHT_BROWSERS_PATH.
569
+ Use the actual script path. Custom browser launch settings need their equivalent exact-runtime
570
+ check. Non-browser capabilities need only their documented runtime checks.
571
+ Never create paid ingredients before the selected renderer passes. Repair and recheck under
572
+ existing setup permissions. If it cannot be fixed, hand the project off in Step 5 (a
573
+ `needs_browser` format can't be: offer another); never silently change the selected format.
574
+ - **Coworker sandbox:** you are the renderer. Follow goose-video-local's sandbox checks; never
575
+ hand off recursively or claim browser capability that the sandbox lacks.
576
+
577
+ ### 5. Create the project and hand it off, in this session
578
+
579
+ 1. `video_project_upsert { brand_id, name, format: <template_id> }` with no brief (a brief creates
580
+ a concept batch). A street interview is created only after its route check found a supported
581
+ setup. Default
582
+ creation_intent:"format" keeps the style with this brand's content; source_remix requires an
583
+ explicit choice to use source content.
584
+ If they already chose a campaign/concept, include its verified campaign_id and optional
585
+ campaign_concept_id. Read the IDs from that brand's saved campaign; the concept must belong
586
+ to it. Omit unknown IDs and never infer a link or create a campaign solely to file a video.
587
+ 2. **You make it** (a shell on the customer's computer, or the coworker sandbox): load
588
+ `catalog_fetch { type: "skill", slug: "goose-video-local" }` and follow it on the same project_id.
589
+ Carry the customer's words, verified defaults, campaign, selected angle and a street
590
+ interview's route check result into its brief.
591
+ 3. **No shell there:** `goose_run_task { brand_id, project_id, message }` with the request, selected
592
+ format, defaults and a street interview's route check result. Keep task_id. Continue questions/edits with the same task and project.
593
+ A saved free draft is not a started worker or a complete plan. Follow actual saved state.
594
+ 4. When follow.card_follows is true, the card follows progress: do not re-read it in a loop.
595
+ Read again on a customer reply, card action or requested update. A failure or missing saved
596
+ plan needs the returned recovery action; never claim completion or approve an empty plan.
597
+ Approval requires the current complete plan and total. Insufficient balance: offer a shorter
598
+ video or top-up. “Not now” keeps the saved plan.
599
+ 5. On the customer's yes, record it with `video_project_upsert { brand_id, project_id, patch:
600
+ { approve: { user_quote, total_credits } } }`. Its reply says what comes next: the coworker
601
+ gets the approval when it is making the video; otherwise you render it.
602
+
603
+ Do not hand the customer a command to paste somewhere else.
239
604
 
240
605
  ## Decision Rules
241
606
 
242
- - **One sentence is a complete request.** Never answer "make me a video ad for X" by asking which format, which tool or what to do next. Run steps 1–3 and let the table do the asking.
243
- - **One brand in the org → never ask which brand.** State the one you used.
244
- - **Open question first, table second.** Don't lead with the whole catalogue. The goal question comes before any list, and the table comes ordered, with one suggestion.
245
- - **No approval of the SCRIPT → do not call `kind: "full"`.** The price gate is not the script gate. They approve a number in step 5 and the actual ad in step 6.
246
- - **More than two options → table in the message.** Every time. Options with a sample or demo the customer can't hear or see are not really choices. A question control may capture the answer after the table, never instead of it.
247
- - **`ready: false` → stop.** Ordering anyway fails and wastes their time.
248
- - **Unsure whether an order went through → read `video_project_read` first.** Never use the charging tool as a status probe. Only if the project shows nothing at all, re-call `video_render_run` on the SAME `project_id` (idempotent per project). Never create a second project; that is a second charge.
249
- - **Quote cards, never paraphrase them.** The card is the server's promise about the format. A reworded card can promise something the format cannot do.
250
- - **A contradiction with the card outranks every keyword match.** Check what they asked for against what the card and the `ask[]` options say the format does; a format that can't do the ask is never Suggested.
251
- - **They asked for a format that isn't in the catalogue** → it isn't available yet. Say so, show the table, and don't improvise a lab recipe.
252
- - **Run failed → say so plainly.** The credits are released automatically. Offer a retry; never retry unasked.
253
- - **Their words go in an `edit`, never in a `redraft` reason.** A `reason` steers the next draft; it is not copied into it. The moment a customer gives you actual copy, the only honest routes are `kind: "edit"` or telling them this format will not take it.
254
- - **Deliver the page, not the file.** `video_url` leads, `mp4_url` follows. A bare CloudFront link is not a delivery.
255
- - **The brief is the customer's words, not yours.** `prompt` is their answer, verbatim or close to it. `audience`, `must_mention` and `avoid` hold only what they said. Another brand in `must_mention` means they asked for it; it can be named as "works with", never as an endorsement or ranking.
607
+ - One sentence is a complete request. Resolve the brand and show formats without a goal interview.
608
+ - One brand in the org: never ask which brand. Cards decide presentation; checks decide execution.
609
+ - Use canonical tools. Preserve campaign context, selected script angles and custom authenticated review gates.
610
+ - Never fabricate readiness, completion, approval or a second project.
256
611
 
257
612
  ## Output
258
613
 
259
- The finished video in the chat: `video_url` (the project page) first, `mp4_url` (the file) second, with its length and ratio.
260
-
261
- The credits leave the balance when the order is placed (a hold). They are **captured** on success or **released** on failure or cancellation. A balance that dropped does not prove a charge, so don't describe it to the customer that way.
614
+ One project, one current saved plan in this chat, then the finished video. With a widget write one
615
+ line and do not add app_url, brand_url or duplicate video links. A text host gets text_summary
616
+ and its usable delivery link.
262
617
 
263
618
  ## Quality Checks
264
619
 
265
- - A one-sentence opening got: the brand resolved (unasked when there is one), one open goal question, then a format table with demo links and one suggestion.
266
- - The table was ordered by the customer's goal, not the catalogue's order.
267
- - Every "What it looks like" cell is the card's own words. No Suggested format's card contradicts what the customer asked for; when nothing fit, you said so and named the closest with its trade-off.
268
- - Every multi-option question was a table in the message, with a demo or sample column wherever one exists.
269
- - Only the picked format's `ask[]` questions were asked.
270
- - The step-2 answer went into the order as `brief.prompt`, and no intent field held anything the customer didn't say.
271
- - They approved the **script**, not just the price, before the expensive work ran: the thread for a chat format, the on-screen words *and* the voiceover for a kinetic-type one.
272
- - You told them how long the render would take **for the format they picked**, and said something at the halfway mark instead of going quiet.
273
- - The quote you showed came from the server, never from memory or the catalogue.
274
- - Every line of copy the customer wrote is in the finished video word for word — it went through `kind: "edit"`, or you told them plainly that this format would not take it. No supplied copy was ever paraphrased into a `redraft` reason.
275
- - The video was delivered in the chat as `video_url` first and `mp4_url` second. You did not send them to the app for anything but payment.
276
- - You asked for no promo code or reference video, and made no product claim the brand's own facts don't support.
620
+ - No forced goal question, duplicate table or unsupported shell claim.
621
+ - The exact renderer passed before local paid ingredients; uncertain assets stay uncertain.
622
+ - Selected campaign and angle survive; a single template project has no brief.
623
+ - Current complete plan and total precede approval. Custom script and ingredient gates remain independent; Studio is optional.
277
624
 
278
625
  ## Failure Modes
279
626
 
280
627
  | Symptom | Cause | Fix |
281
628
  | --- | --- | --- |
282
- | "Make me a video ad for X" got back "which format / what would you like?" | Treated the one-liner as incomplete | Run steps 1–3 unprompted: resolve the brand, ask the goal, show the table |
283
- | Asked which brand in a one-brand org | Skipped the count check in step 1 | Use the only brand and say which |
284
- | Opened with the full format list | Skipped the goal question | Ask what the ad is for first; order the table by the answer |
285
- | Demo links invisible to the customer | The choices went only into the structured question control | Table in the message first; the control only takes the answer |
286
- | `format_not_available` | Not an orderable format | Re-read the catalogue; offer what's in it |
287
- | `brand_not_ready` | Brand has no product photos or logo | Relay `missing` in plain words; stop |
288
- | `invalid_brief` | The brief carried a key this format doesn't accept (e.g. another format's question, or a made-up field like `tone`) | The message lists what is accepted. Move the customer's words into `prompt`/`audience`/`must_mention`/`avoid`; never drop them |
289
- | The script ignores what they asked for | The step-2 answer wasn't sent (it only sorted the table), or they added something later that never reached the brief | Send it as `brief.prompt` in step 5. Anything they add later goes in with `patch.brief` (`notes`) and then a redraft. The preview's `brief_check` shows missing must-mention terms |
290
- | Video not on this plan | Lite and trial have no video entitlement | Say so and point at the upgrade; this is the one app trip that's allowed |
291
- | Insufficient credits | Wallet short | Nothing was created or charged; report the shortfall |
292
- | Preview looks wrong | The script or the picture isn't what they wanted | They gave you the words → `kind: "edit"`. They changed the brief → `patch.brief` then a redraft with no reason. They only said what's wrong → `kind: "redraft"` with their reason. Same order, no new hold, a few credits inside it; never "free". Or `job_cancel` this one |
293
- | **The customer wrote the script and the finished video says something else** | Their lines went into a `redraft` `reason`. A redraft re-runs the writer: the reason steers the next draft, it is never copied into it. Seen on staging — "Keep this exact draft, use these lines: …" came back paraphrased, twice, and was paid for both times | Copy goes in `kind: "edit"`. If the format refuses it (`script_not_editable`), say so and let them choose a redraft, a re-order or cancel — knowing the words will be new |
294
- | Video started rendering before the customer approved | `kind: "edit"` on a preview applies the copy **and** starts the full render (status → `running`) | Show the exact words you're about to send, get a yes, then send the edit. An unapproved fix stays a proposal in the chat |
295
- | `draft_is_your_copy` on a redraft | The current draft is the customer's own edited copy; another draft would replace their words | Correct behaviour. Edit their copy again with `kind: "edit"`, approve it, or cancel |
296
- | `script_rejected` on an edit | A line makes a claim the brand's facts don't support | Relay the reason verbatim and offer a rewrite; nothing was charged |
297
- | The customer was sent a raw `cloudfront.net/….mp4` | Delivered `mp4_url` (or `order.output_url`) instead of `video_url` | `video_url` leads and `mp4_url` follows. Both come back from `video_project_read` once `order.status` is `done` |
298
- | `recipe_brief_locked` | You patched `script_drafts` raw on a format project, which would have skipped the brief's checks | Send the change as `patch.brief` instead |
299
- | `format_answers_locked` | After ordering, you tried to change the format's own answer (voice, angle, selfie) through the brief | Those change what the run costs. Use `kind: "edit"` where the format lists it, or start a new project |
300
- | Asked the customer three rounds of questions before anything was made | Treated `questions` as a form, or asked your own follow-ups | Ask what `questions` holds, once, in one message. Nothing else. Skipping is fine |
301
- | `preview_in_progress` | You called `full` while the script was still being written | Keep polling `video_project_read` until `order.preview` appears |
302
- | Cancel refused | The order is already `running`; they approved it | Say it's being made; a refund isn't possible now |
303
- | No render after the format's budget (10 min chat, 20 min kinetic) | The worker didn't pick the run up | Credits are held, not spent. Say it's queued and you'll follow up; don't re-order |
304
- | `preview_not_available` on an orderable format | A bug; every sold format has a script preview | Say so and stop. Do not order without showing the script |
305
- | An edited script is refused (`script_not_editable`) | This format's writer takes no hand edit (its `edits.script` is empty). Only some formats do | Should have been said at the table. Their words cannot be rendered verbatim here: offer a redraft (new words), a re-order with different answers, or cancel — and never quietly paraphrase their copy into the redraft reason |
306
- | A "hero, no villain" brief got the villain cartoon | The card was paraphrased ("narrates the fix") and the fit check never read what the format can't do | Quote `card.description`; check the ask against the card and `ask[]`; never Suggest a contradiction |
307
- | Two videos, two charges | A second project was created instead of continuing the first | Continue on the SAME `project_id` |
629
+ | Picker plus table | Ignored display_hint | Widget: one line; otherwise text_summary |
630
+ | Agent with a shell hands off | Decided from the host, not the rule | Make it here; hand off only without a shell or a fixable toolchain |
631
+ | Chat told it cannot start | Only local execution considered | Create once and delegate to coworker |
632
+ | Worker ends without a plan | Assumed completion means ready | Follow actual failure/recovery state |
633
+ | Two projects for one video | Recreated instead of resuming | Continue same project and task |
634
+
635
+ ## Use uploaded footage
636
+
637
+ When the customer asks to use existing footage, check the actual advertised schemas. Use `media_search` with purpose `production`, kind `video`, the brand and current project when needed. Omit query spend for free retrieval. Read facts/scenes with `media_analyze`, then use `media_excerpt` action `inspect` to review actual bounded frames and timed transcript before selecting. A description or thumbnail URL alone is not visual review.
638
+
639
+ Freeze `source_excerpt:{asset_id,analysis_revision,scene_id,start_ms,end_ms,audio_mode}`. Revision is the original-byte SHA, scene may be null for a known user trim, bounds use integer milliseconds, and audio is original or muted. Call `media_excerpt` action `attach` with that exact selection and a stable idempotency key. The returned project remains unfinished and unapproved. Open its existing Studio review; preserve user locks and requested format, and keep footage separate from image packshot indexes. An incompatible format requires an explicit choice before changing it.
308
640
 
641
+ For custom production, fetch the current shared `video-production-harness` and follow script/ingredient gates. Download the verified original and trim its exact selected window at normal speed with the approved audio. Recheck current revision and production permission before consumption and final upload/completion. Save excerpt lineage in plan and ingredient readback. Missing optional semantics does not require another upload; known user-selected stored footage remains usable. Research and competitor references never become production footage. Selected originals require no paid generation; only newly generated/replaced ingredients consume the approved budget.