Add Beryl Agent and harness source

This commit is contained in:
2026-09-08 20:10:03 +08:00
parent 06d72d001c
commit 47cfbcd66a
70 changed files with 12558 additions and 0 deletions
@@ -0,0 +1,122 @@
# Failure modes measured on this repository
The rules live in `SKILL.md`. This file is what was actually observed, with the
numbers. Read it when a rule looks arbitrary, or when you are tempted to fix a
bad render by doing more of what caused it.
## Negation summons the object — 2026-08-17
A pitcher-and-cups shot kept growing extra pouring vessels, so the retention
line was hardened with an explicit ban:
```
no second pitcher, no fifth cup, no jug, no bottle, no carafe
```
The next render was **worse**: the pitcher now poured *into another pitcher*,
the receiving cup having grown a spout. Deleting the whole list and rewriting
the same constraint positively passed on the first try.
The text conditioning does not parse negation — `no jug` and `jug` enter
cross-attention as the same token. Negating an *attribute* (`no lip`) is safe;
what backfires is negating a whole object noun.
What worked instead: the pitcher became "the only vessel in this video that has
a pouring lip"; the cups became `no lip and no handle` and `only ever receives
liquid`; the count was stated positively as `the whole table holds five glass
objects in total`.
## `<Picture N>` entries and `sources` — 2026-08-10
Both `qwen3.8-max` and `claude-opus-5` hit the same rejection three times each
and burned five attempts between them.
The cause was the instruction, not the models. A picture's source is itself, so
`sources: <Picture 1>` is a tautology — and the one thing a picture definition
must do is say who is in that frame, which forces it to mention that the same
person also appears in `<Picture 2>`. The set becomes `[1,2]` and the entry is
rejected for a contradiction the format invented.
**The same error from two different model tiers means the instruction is
ambiguous, not that the model is too weak.** Reaching for a bigger model there
is the wrong move. Read the failing constraint and ask whether it carries any
information at all.
## What freehand prompts produced
Observed on this repository's own production runs, each costing a re-render the
Ref2VA structure would have caught for free:
- strands of vegetable appearing with no vegetable in frame
- two copies of a single-item product in one shot
- two tools merging onto one subject instead of staying on opposite sides of
the board
## Audio collapse is a dice roll — 2026-08-09
One beat, an alarm bell held for the full 5 s, same prompt both times:
| | collapsed | re-rolled |
|---|---|---|
| last 1.4 s | 8.8 dB — loudest point in the clip | 16.5 dB |
| span across the clip | 25.8 dB | 6.5 dB |
| clipped samples | 844 | 85 |
Two explanations looked equally sound at the time — the sound was described too
vaguely, and H3 cannot hold a sustained high-energy ambience. Both would have
sent someone rewriting the prompt scaffold that every other clip depends on.
One re-render falsified both: same wording, same sustained requirement, clean
take.
One collapsed sample is never grounds for changing the scaffold. The trigger for
that is the same prompt collapsing twice in a row — that is the difference
between a bad die and a bad prompt.
## Object count is set by canvas size — 2026-08-20
A pitcher-and-four-cups delivery, the same product family as the negation case
above. The brief's hard lock was "exactly one pitcher and four cups, countable".
The riskiest shot was probed three times at `--megapixels 0.3`. All three came
back with exactly four cups. The same prompts at `1.03` grew a fifth in three of
the six clips. Nothing about the prompt changed — only the canvas. At the low
tier the frame is small and the model fills it with what was asked for; at full
tier the extra horizontal room gets filled too.
So a cheap probe is evidence about motion, structure and composition, and no
evidence at all about how many things appear.
What finally held was a container, not a phrase. The four cups were placed on a
serving board defined as *four cups long and one cup deep, covered end to end*.
Where the whole board stayed inside the frame the count was correct on the first
render. Where a camera move cropped an end, the count drifted into the cropped
space:
| shot | camera | attempts to correct |
|---|---|---|
| pitcher lift | fixed wide, whole board in frame | 1 |
| pour | fixed wide, whole board in frame | 1 |
| final settle | opens wide, only widens | 1 |
| ice drops | close-up, then truck, then "static wide" wording | 3, fixed only by reusing a working shot's establishing geometry verbatim |
The last row is the useful one. Three separate rewordings of "keep all four in
frame" did not move it. Copying the opening sentence from a shot that already
rendered four correctly did. When an instruction has failed twice, stop
rewording it and transplant the wording that works.
## The batch fingerprint hashed filenames — 2026-08-20
Same delivery. All six prompts were rewritten to fix the product-lock violation
above, and `batch` returned `skip: unchanged` for all six: `job_fingerprint`
hashed the prompt file's *path*, while its own docstring claimed it covered the
same inputs as the single-render fingerprint, which hashes the prompt *text*.
The stale clips were caught by frame review, not by the tool. Fixed in
`src/piecesai/h3/batch.py` — the fingerprint now digests prompt contents and
reference bytes — with regression tests for a rewritten prompt, an edited
anchor, and a moved-but-unchanged prompt file.
The general shape is the one already on the seed comparison: **a fingerprint
match is a claim about work, and a claim you did not check is a result you did
not get.** The seed version costs a wasted A/B. This version silently returns
content you already know is wrong.
@@ -0,0 +1,64 @@
# How the render settings were chosen
The rules live in `SKILL.md`. This file is the evidence behind them — read it
when you are about to argue with a default, not before every render.
The authority for the numbers is the comment block above
`U06_V4_SAMPLING_STEPS` in `src/piecesai/generators/minimax_h3.py`. When the
code default moves, that comment moves with it and this file is stale until it
is rewritten from there. It has been stale once already: three commits of
default changes landed after the first version was written, and it went on
telling readers that the shipped default had been "rejected on motion".
## The v4 step600 LoRA at 8 steps
Chosen 2026-08-16 by side-by-side viewing across four rounds.
Everything before that round used the **fl2v** LoRA — trained for first/last
frame work, while this deployment renders ref2va. Once that mismatch was fixed
the comparison stopped being a speed-versus-quality trade, and two candidates
were left: the ref2v-matched 4-step weight, and v4 step600 at 8 steps.
| round | owner picked |
|---|---|
| peeler, seed 661120 | ref2v + v4 |
| peeler, seed 314159 | no-LoRA + v4 |
| two-person exchange | ref2v + v4 |
| Malay piece to camera | ref2v + v4 |
v4 is the only one present in every round. The ref2v-matched weight scored
higher more often but dropped out when the seed changed, and a twelve-clip
delivery cannot depend on drawing a good seed. The steadier of two equals wins.
8 steps is v4's own contract: its documentation reports motion smear at 4 steps
under large fast motion, largely gone by 6-8, and no gain past 8. Measured on a
5090 at 1.03 MP:
| setting | seconds per clip |
|---|---|
| v4 step600 @ 8 steps (current default) | 119-122 |
| old fl2v default | 128 |
| undistilled @ 14 steps | 156 |
Strength is tuned for 1.0 by the LoRA's own documentation. The 0.75 that shipped
before sat below the documented range and under-corrected the velocity
prediction, which is exactly what smears on large motion.
## Why a sharpness metric was rejected
A sharpness proxy was measured here once and ranked the runs the wrong way. It
scored horizontal high-frequency energy, and over-sharp grain is precisely the
failure mode these LoRAs name in their own documentation — so the metric
rewarded the artefact.
Motion coherence needs eyes. Render the variants, show them, let the owner pick.
## Why an A/B needs a fresh seed
A render whose request fingerprint matches a completed run resumes from that
run's receipt and returns the old file in seconds. Correct for resuming a batch;
wrong for a comparison.
This cost two rounds on 2026-08-16. One set of four variants came back
pixel-identical. Another "finished" in 3-5 seconds. Both times the giveaway was
the clock, not the picture — the frames looked plausible either way.
@@ -0,0 +1,101 @@
# Spoken language support
H3 renders its own audio. The words in `<d>…</d>` are *spoken by the model*,
not dubbed on afterwards, so the language of a line is a model capability
question — not a localisation question that post-production can fix.
## The eleven stable languages
MiniMax's own model card states:
> Stable support for 11 languages: Arabic, Chinese, English, French, German,
> Italian, Japanese, Korean, Portuguese, Russian, and Spanish.
> Additional languages are also supported to varying degrees.
Source: <https://huggingface.co/MiniMaxAI/MiniMax-H3>, checked 2026-08-20.
Use exactly these names as the tag inside `<d>`:
```
<d>[English] First batch of the morning.</d>
<d>[Chinese] 今天的第一炉。</d>
```
## What the limit covers, and what it does not
- **Covered:** every spoken or sung line the model performs — dialogue,
voiceover narration, lyrics. Anything inside `<d>…</d>`.
- **Not covered:** the prompt body itself. All six rewrite sections stay in
English regardless of the campaign language — that is `h3-prompt-writing`'s
rule and this gate does not change it.
- **Not covered:** on-screen text. Signs, lower thirds and product copy are a
*glyph rendering* problem, not a speech one. A language can be outside the
eleven and still render as visible text, and a language inside the eleven can
still render its glyphs badly. Judge on-screen copy from a still.
- **Not covered:** `overall_soundscape` and `non_diegetic_music`. Wordless
audio has no language.
"Supported to varying degrees" is the vendor's phrasing for the rest. It is
not a promise and it is not a refusal. Treat an out-of-list language as an
untested render, priced like one.
The failure modes to watch for on an out-of-list line are accent drift toward
the nearest stable language, substituted phonemes, lip movement that no longer
matches the words, and — occasionally — the line coming back in English. None
of these are measured on this repo's fleet; they are what the probe below is
for.
## Subtitles are outside this gate, and outside the toolchain
Delivery subtitles carry no model risk in any language — they are not spoken.
But no subtitle burn-in tool ships here: `post` has `verify`, `assemble`,
`frames` and `derive` and nothing else. Burned-in captions are hand-written
ffmpeg work. Price them as work, never as an existing step.
## The gate
Fires the moment a spoken language is chosen — at the brief, not at render
time. The official creative Skills ask for narration language early
(`brand-promo-video-generator` asks in Step 1, alongside duration and aspect
ratio); that answer is the trigger.
`uv run piecesai h3 preflight --language <name> ...` answers this without
reading anything: the language check is one of its findings, and it warns
rather than fails, which is exactly the gate described here.
If the language is one of the eleven, say nothing and carry on.
If it is not, this is a **soft** gate. Do not refuse, do not silently swap the
language, and do not quietly drop the voiceover. Tell the user plainly what
the constraint is and let them pick:
1. **Keep the language, probe first.** Render the single densest dialogue
shot at low `--megapixels` and listen to it before committing the batch.
Cheapest way to turn the question into an answer.
2. **Speak a stable language, subtitle the target one.** Voice the line in
the nearest stable language and carry the campaign language as subtitles.
Usually the right answer for a promo, where the read is short and the copy
carries the message. Quote the subtitles as work — see above.
3. **Drop the spoken layer.** Music, soundscape and on-screen copy only.
Costs nothing in render risk and often suits a 15-second promo better than
a rushed voiceover.
Record which one the user chose next to the prompt, the same way the prompt
itself is kept. When a later shot comes back with wrong-sounding speech, that
line is the difference between a known trade-off and a mystery.
## The probe
```bash
uv run piecesai h3 render \
--project-id <proj_id> --title "lang probe" --skill <calling-skill-name> \
--prompt-file <path> --reference <anchor.png> \
--h3-mode ref2va --duration 5.0 --megapixels 0.3
```
Pick the shot with the most words per second, not the first shot. Listen for
the words themselves, and check the mouth against them — a line can be
intelligible and still be lip-synced to a different language's phonemes.
A failed probe is not a fleet problem. Do not load `h3-fleet-ops` for it.
Go back to the user with what you heard and pick option 2 or 3.